【问题标题】:Thread-safe Factory pattern without control over Derived classes不控制派生类的线程安全工厂模式
【发布时间】:2018-07-27 06:20:24
【问题描述】:

假设我有一个类 'Car' 有一个复杂的子类树。每个类都有一个唯一的 id。我有一个管理类,它跟踪所有汽车的指针,并可以根据id 查找它们。

class Car {
public:
    using CarId = size_t;
    Car* getCar(CarId id) const { return sMap[id]; }
    Car(){
        sMutex.lock();
        mId = ++sLastId;
        sMap.insert(pair(mId,this));
        sMutex.unlock();
    }
private:
    static map<CarId,Car*> sMap;
    static CarId sLastId;
    static mutex sMutex;
    CarId mId;
}

我想对此进行重构,以便 Car::getCar 返回 weak_ptr&lt;Car&gt;,因为返回原始指针会导致很多问题。

但是,要以一种天真的方式从构造函数将指向自身的弱指针添加到sMap 中,需要创建一个共享指针,它会在对象超出范围时立即销毁它:

sMutex.lock();
mId = ++sLastId;
sMap.insert(
    pair(mId,
         weak_ptr<Car>(shared_ptr(this))
    )
);
sMutex.unlock();

该死,我想我需要使用一些工厂:

class CarFactory {
public:
    shared_ptr<Car> createCar(){
        sMutex.lock();
        mId = ++sLastId;
        auto res = shared_ptr<Car>(this);
        sMap.insert(
            pair(mId,
                 weak_ptr<Car>(res)
            )
        );
        sMutex.unlock();
        return result;
    }

private:
    map<CarId,Car*> sMap;
    CarId sLastId;
    mutex sMutex;
}

很好,但是有很多派生类,很多时候在我不能从这里包含的模块中,所以我不能为这里定义的每个可能的 Car 派生类型都有一个工厂方法,这将是非常不切实际的。但是我需要跟踪每辆汽车,我不能允许任何不通过工厂方法创建派生汽车的方法。

迄今为止我想出的最佳解决方案:

class CarFactory {
...
template<class DT, class Tp>
shared_ptr<DT> create(Tp params...)
{
    sMutex.lock();
    mId = ++sLastId;
    auto res = shared_ptr<Car>(new DT(forward(params)...) );
    sMap.insert(
        pair(mId,
             weak_ptr<Car>(res)
        )
    );
    sMutex.unlock();
    return result;
}
...
}

允许将派生类构造函数私有化:

class VolksWagenGolf : public VolksWagen {
    friend class CarFactory;
private:
    VolksWagen(bool enableEmissionCheat);
}

因此我只能按如下方式创建它:

shared_ptr<Car> myCar = CarFactory::create<VolksWagenGolf>(true);

是的,但我还是不喜欢它,因为:

  1. 它是一个模板,出于各种原因,最好不使用模板。
  2. 任何时候,不知道这种机制的同事都可以像以前那样使用公共构造函数来声明新的 Car 类型。但是现在当他使用那个公共构造函数时,他的汽车将不会被跟踪。

因此,如果您有任何想法如何实现这样一种模式,即每个派生类都必须通过集中记账机制创建,同时使用弱指针进行跟踪,请告诉我。

【问题讨论】:

  • Cars 构造函数设为私有,将CarFactory::create 设为friend 可以解决问题2。
  • 但是如果我将 Car 的构造函数设为私有,那我根本不能继承它,不是吗?
  • 你是对的。那是一个大脑放屁。

标签: c++ c++11 refactoring


【解决方案1】:

第 1 步:创建受保护的汽车构造函数。它需要您的工厂函数拥有的令牌:只有工厂函数有权创建该令牌。

如果没有该令牌,现在没有任何代码可以尝试创建派生汽车。

class CarFactory {
private:
  struct CarConstructionPermission {
    explicit CarConstructionPermission(int) {};
  };
  friend class Car;
public:
  template<class D, class...Args>
  std::shared_ptr<D> create(Args&&...args) {
    // blah blah
    auto retval = std::make_shared<D>( CarConstructionPermission(0), std::forward<Args>(args)... );
    // blah
    return retval;
  }
};
class Car {
protected:
  explicit Car(CarConstructionPermission) {}
};

第 2 步:将模板主体部分隐藏。

你需要的是一个从 () 到 shared_ptr 的函数。

private:
  bool create_internal(std::function< std::shared_ptr<Car>() > producer);

public:
  template<class D, class...Args>
  std::shared_ptr<D> create(Args&&...args) {
    std::shared_ptr<D> retval;
    if (create_internal([&]{
      retval = std::make_shared<D>( CarConstructionPermission(0), std::forward<Args>(args)... );
      return retval;
    }) {
      return retval;
    }
    return {}; // failure
  }

现在所有的互斥量都在create_internal 中;我们输入擦除创建汽车并将其传递。

create_internal 现在看起来很像您的 create,但不是模板,返回 bool,并接受 1 个参数。 auto result = producer(); 替换 shared_ptr&lt;Car&gt; 行。

【讨论】:

  • 太棒了!我在这里看到的唯一问题是,如果我想创建一个派生类class Ford : public Car,我需要声明一个构造函数Ford(CarConstructionPermission)。但是,如果许可在工厂内是私有的,我可以这样做吗?无论如何,我想即使它不能按原样工作,我也可以通过公开 Permission 并且只有它的构造函数是私有的(Factory 作为 Permission 的朋友)来修复它。非常感谢。
  • @jlan ya,我搞砸了。私有 ctor,类型是公共,汽车是朋友是解决方案。
猜你喜欢
  • 1970-01-01
  • 2020-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多