欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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