与AI合作 -- 写一个modern c++单例工厂2

1. 前言

上节我们完成了一个单例工厂类,单例和工厂合到一起了。本节我们将分别实现它们,便于代码重用。OKay,AI助手上场。

2. 单例单拎出来

mzhai: Write a class to implement singleton design pattern using modern c++.

bard: Here's a C++ class implementing the singleton design pattern using modern techniques:

#include <mutex>class Singleton {
public:static Singleton& getInstance() {std::lock_guard<std::mutex> lock(mutex); // Acquire lock for thread safetystatic Singleton instance; // Local static variable for thread safetyreturn instance;}// Prevent copying and moving to ensure a single instanceSingleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;Singleton(Singleton&&) = delete;Singleton& operator=(Singleton&&) = delete;protected:Singleton() = default; // Private constructor for singletonprivate:static std::mutex mutex; // Mutex for thread-safe initialization
};// Initialize the static mutex member outside the class
std::mutex Singleton::mutex;

关于单例模式,比较简单而且这个模式很成熟,网上有大量资料。所以bard这个问题回答的很好,

2.1 static变量要锁保护吗?

只是有一点有点怀疑:static Singleton instance需要锁保护吗?要回答这个问题,首先必须知道这个变量什么时候初始化?

1.在C++中,全局static变量和class的static成员变量在main函数之前初始化,main函数之后销毁。

2. 函数内部的局部static变量在该函数第一次被调用时初始化,在main函数之后销毁

3.对于static数据即使是函数内部的对象则存放在全局数据区,全局数据区的数据并不会因为函数的退出就将空间释放。

如果两个线程同时调用Singleton::getInstance会同时初始化Singleton instance,可能会造成问题。这个问题我们以后再细细研究。此处先认为加锁更好。

2.2 函数中的static变量何时初始化?

还有一个问题:Singleton instance是在函数getInstance已进入就初始化?还是运行到这一句才初始化?通过一个小小的试验就会有答案:

class Product {
public:Product(){std::cout<<"Product constructor"<<std::endl;}
};void test(){std::cout<<"I am in test()"<<std::endl;static Product pro;std::cout<<"leaving test()"<<std::endl;
}int main() {test();test();return 0;
}

为什么要问这个问题哪?因为如果函数内static变量一上来就初始化(跳到所有函数内语句之前)将会造成lock时机晚了。还好没有这个问题。

3. 改造我们的工厂类

有了上面的单例类,我们的工厂类就可以去掉getInstance()函数了,只需要继承上面的单例类。但问题是Singleton::getInstance创建出来的是Singleton对象而不是工厂对象,只是“半个”工厂,怎么办?

3.1 先改造Singleton类

这里就必须用CRTP思想了,《Modern C++ code snippets》提到过。就是把子类当做父类的模板参数,这里好让子类创建出来的对象类型是子类。首先要改造Singleton, 给它加一个模板参数:

#include <mutex>template<typename T>
class Singleton {
public:static Singleton& getInstance() {std::lock_guard<std::mutex> lock(mutex); // Acquire lock for thread safetystatic T instance; // Local static variable for thread safetyreturn instance;}// Prevent copying and moving to ensure a single instanceSingleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;Singleton(Singleton&&) = delete;Singleton& operator=(Singleton&&) = delete;protected:Singleton() = default; // Private constructor for singletonvirtual ~Singleton()        = default;private:static std::mutex mutex; // Mutex for thread-safe initialization
};// Initialize the static mutex member outside the class
std::mutex Singleton<T>::mutex;

3.2 改造工厂类:

去掉ProductFactory::getInstance, 继承Singleton<ProductFactory>

class ProductFactory:public Singleton<ProductFactory> {
public:template <typename T, typename... Args>//typename std::enable_if<std::is_same<Product,T>::value, void>::typestd::unique_ptr<T> createProduct(Args&&... args) {return std::make_unique<T>(std::forward<Args>(args)...);}private:ProductFactory() = default; // Private constructor for singletonfriend class Singleton<ProductFactory>; //方便父类getInstance调用上一行的private构造函数
};

好像自己继承了自己似的,这也是CRTP中CR的意思(奇异递归)。如果想了解CRTP请自行查阅资料。

4. 全部代码

#include <iostream>
#include <memory>
#include <utility>
#include <mutex>class Product {
public:virtual ~Product() = default;virtual void use() = 0;
};// Concrete products with different arguments
class Product1 : public Product {
public:Product1(int arg1) : arg1_(arg1) {}void use() override { std::cout << "Using Product1 with arg1: " << arg1_ << std::endl; }
private:int arg1_;
};class Product2 : public Product {
public:Product2(int arg2_1, std::string arg2_2) : arg2_1_(arg2_1), arg2_2_(arg2_2) {}void use() override { std::cout << "Using Product2 with arg2_1: " << arg2_1_ << ", arg2_2: " << arg2_2_ << std::endl; }
private:int arg2_1_;std::string arg2_2_;
};template<typename T>
class Singleton{
public:static T& getInstance(){std::lock_guard<std::mutex> lock(mutex); // Acquire lock for thread safetystatic T inst; //make sure this parent class can access T's private constructor function by making friends.return inst;}Singleton(T&&)                = delete;Singleton(const T&)            = delete;void operator= (const T&)    = delete;void operator= (const T&&)    = delete;protected:Singleton()                    = default;virtual ~Singleton()        = default;private:static std::mutex mutex; // Mutex for thread-safe initialization
};template<typename T>
std::mutex Singleton<T>::mutex;class ProductFactory:public Singleton<ProductFactory> {
public:template <typename T, typename... Args>//typename std::enable_if<std::is_same<Product,T>::value, void>::typestd::unique_ptr<T> createProduct(Args&&... args) {return std::make_unique<T>(std::forward<Args>(args)...);}private:ProductFactory() = default; // Private constructor for singletonfriend class Singleton<ProductFactory>;
};int main() {ProductFactory& factory = ProductFactory::getInstance();std::unique_ptr<Product> product1 = factory.createProduct<Product1>(42);std::string hi("Hello");std::unique_ptr<Product> product2 = factory.createProduct<Product2>(5, hi);product1->use();product2->use();return 0;
}

$ ./a.out
Using Product1 with arg1: 42
Using Product2 with arg2_1: 5, arg2_2: Hello