C++ 单例模式讲解和代码示例
程序员文章站
2022-07-13 23:46:10
...
单例是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
单例拥有与全局变量相同的优缺点。 尽管它们非常有用, 但却会破坏代码的模块化特性。
在某些其他上下文中, 你不能使用依赖于单例的类。 你也将必须使用单例类。 绝大多数情况下, 该限制会在创建单元测试时出现。
基础单例
实现一个粗糙的单例非常简单。 你仅需隐藏构造函数并实现一个静态的构建方法即可。
相同的类在多线程环境中会出错。 多线程可能会同时调用构建方法并获取多个单例类的实例。
main.cc: 概念示例:
/**
* Singleton类定义了用作构造函数的替代方法,并允许客户端反复访问此类的同一实例。
*/
class Singleton
{
/**
* Singleton的构造函数应始终是私有的,以防止直接调用'new'运算符进行构造。
*/
protected:
Singleton(const std::string value): value_(value)
{
}
static Singleton* singleton_;
std::string value_;
public:
/**
* “Singleton”不应该是可克隆的。
*/
Singleton(Singleton &other) = delete;
/**
* “Singleton”不应该是可分配的。
*/
void operator=(const Singleton &) = delete;
/**
* 这是控制对singleton实例的访问的静态方法。
* 在第一次运行时,它创建一个单例对象并将其放置到静态字段中。
* 在后续运行时,它返回存储在静态字段中的客户机现有对象。
*/
static Singleton *GetInstance(const std::string& value);
/**
* 最后,任何单例都应该定义一些可以在其实例上执行的业务逻辑。
*/
void SomeBusinessLogic()
{
// ...
}
std::string value() const{
return value_;
}
};
Singleton* Singleton::singleton_= nullptr;;
/**
* 静态方法应该在类之外定义。
*/
Singleton *Singleton::GetInstance(const std::string& value)
{
/**
* 这是创建实例的更安全的方法。
* 如果两个实例线程想要同时访问,“instance=new singleton”是危险的
*/
if(singleton_==nullptr){
singleton_ = new Singleton(value);
}
return singleton_;
}
void ThreadFoo(){
// 下面的代码模拟缓慢的初始化。
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("FOO");
std::cout << singleton->value() << "\n";
}
void ThreadBar(){
// 下面的代码模拟缓慢的初始化。
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("BAR");
std::cout << singleton->value() << "\n";
}
int main()
{
std::cout <<"如果您看到相同的值,那么singleton被重用。"
"如果看到不同的值,则创建了2个单例\n\n RESULT:\n";
std::thread t1(ThreadFoo);
std::thread t2(ThreadBar);
t1.join();
t2.join();
return 0;
}
Output.txt: 执行结果:
如果您看到相同的值,那么singleton被重用。如果看到不同的值,则创建了2个单例
RESULT:
BAR
FOO
线程安全单例
为了解决这个问题, 你必须在创建首个单例对象时对线程进行同步。
main.cc: 概念示例:
/**
* Singleton类定义了“GetInstance”方法,该方法作为构造函数的替代方法,
* 并允许客户端反复访问此类的同一实例。
*/
class Singleton
{
/**
* Singleton的构造函数/析构函数应始终是私有的,
* 以防止使用'new`/'delete`运算符直接调用构造/析构函数。
*/
private:
static Singleton * pinstance_;
static std::mutex mutex_;
protected:
Singleton(const std::string value): value_(value)
{
}
~Singleton() {}
std::string value_;
public:
/**
* "Singletons" 不应该是可克隆的。
*/
Singleton(Singleton &other) = delete;
/**
* "Singletons" 不应该是可转让的。
*/
void operator=(const Singleton &) = delete;
/**
* 这是控制对singleton实例的访问的静态方法。
* 在第一次运行时,它创建一个单例对象并将其放置到静态字段中。
* 在后续运行时,它返回存储在静态字段中的客户机现有对象。
*/
static Singleton *GetInstance(const std::string& value);
/**
* 最后,任何单例都应该定义一些可以在其实例上执行的业务逻辑。
*/
void SomeBusinessLogic()
{
// ...
}
std::string value() const{
return value_;
}
};
/**
* 静态方法应该在类之外定义。
*/
Singleton* Singleton::pinstance_{nullptr};
std::mutex Singleton::mutex_;
/**
* 第一次调用GetInstance时,我们将锁定存储位置,
* 然后再次确保变量为null,然后设置值。RU:
*/
Singleton *Singleton::GetInstance(const std::string& value)
{
std::lock_guard<std::mutex> lock(mutex_);
if (pinstance_ == nullptr)
{
pinstance_ = new Singleton(value);
}
return pinstance_;
}
void ThreadFoo(){
// 下面的代码模拟缓慢的初始化。
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("FOO");
std::cout << singleton->value() << "\n";
}
void ThreadBar(){
// 下面的代码模拟缓慢的初始化。
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
Singleton* singleton = Singleton::GetInstance("BAR");
std::cout << singleton->value() << "\n";
}
int main()
{
std::cout <<"如果您看到相同的值,那么singleton被重用。"
"如果看到不同的值,则创建了2个单例\n\n RESULT:\n";
std::thread t1(ThreadFoo);
std::thread t2(ThreadBar);
t1.join();
t2.join();
return 0;
}
Output.txt: 执行结果:
如果您看到相同的值,那么singleton被重用。如果看到不同的值,则创建了2个单例
RESULT:
FOO
FOO
上一篇: 单例模式的实现