【问题标题】:Singleton class implementation using shared_ptr使用 shared_ptr 实现单例类
【发布时间】:2020-12-01 03:48:25
【问题描述】:
#include <iostream>
#include <memory>

using namespace std;

class Demo {
    static shared_ptr<Demo> d;
    Demo(){}
public:    
    static shared_ptr<Demo> getInstance(){
        if(!d)
        d.reset(new Demo);
        return d;
    }
    ~Demo(){
        cout << "Object Destroyed " << endl;
    }

};

//    shared_ptr<Demo> Demo::d(new Demo); // private ctor is accepted 

shared_ptr<Demo> Demo::d;

int main()
{
    shared_ptr<Demo> d(Demo::getInstance());
    cout << d.use_count() << endl;

   return 0;
}
  1. 这是使用 shared_ptr 实现单例类的正确方法吗
  2. 请参阅上面的注释行来初始化静态 shared_ptr 我们为什么可以在这里创建一个对象来使用私有构造初始化 shared_ptr

【问题讨论】:

  • 单例更像是一种反模式,所以我建议根本不要实现它们。它们与全局变量没有什么不同。
  • 虽然很多人都同意不明智地使用单例可能会产生问题,但我认为在这里批评它们并不是特别有用——它无助于提问者解决问题。

标签: c++11


【解决方案1】:

这不是线程安全的:两个线程调用getInstance 会导致数据竞争。一种常见的方法是使用函数范围的静态变量:

static shared_ptr<Demo> getInstance(){
  static shared_ptr<Demo> d(new Demo);
  return d;
}

当控制第一次通过它的定义时,保证这样的变量只被初始化一次,并且以线程安全的方式。

不过,目前还不清楚为什么要使用shared_ptr。你也可以这样做

static Demo& getInstance(){
  static Demo d;
  return d;
}

这是一个单例的教科书实现(嗯,其中之一)。


Re:使用私有构造函数进行初始化。我不确定我是否理解您的困惑的性质。你是在问为什么Demo::getInstance 可以使用Demo 的私有构造函数?好吧,因为它是Demo 的成员,并且类的成员可以访问该类的私有成员。你在问为什么Demo::getInstance 可以通过Demo* 指针调用shared_ptr&lt;Demo&gt;::reset()?嗯,因为reset()shared_ptr的公共成员函数,以指针为参数。你觉得这个过程的哪一部分有争议?

【讨论】:

  • getInstance() 的静态变量不起作用,我认为
  • @IgorTandetnik,我在多线程紧密循环中测试了您的 getInstance() ,它运行良好!但是你能解释一下发生了什么吗?为什么“Demo”只实例化一次?不检查 nullptr,这是什么魔法?
  • @CarolineBeltran 有趣的是你应该这么说。 C++ 语言的这一特性通常被称为"magic statics"。编译器会自动生成执行必要同步的代码。
  • @IgorTandetnik,现在清楚多了,谢谢。我目前将共享指针传递给 Asio 异步应用程序中的单个数据库池实例。它工作得很好,但看起来很凌乱。使用单例会更干净,但是关于多线程/单例应用程序的历史负面信息太多了。在我的紧密循环(数亿次迭代)中,我遇到了零问题,但我想知道您是否会信任您的应用程序中的单例数据库实例,尤其是在 c++ 11 线程安全静态初始化保证的情况下。再次感谢。
  • @CarolineBeltran 我确实在我的项目中使用了单例实例。他们都没有碰巧管理数据库连接,但我不明白为什么会有所作为。
【解决方案2】:

我上面的第二个问题是,在实例化静态成员时,私有构造函数如何在类外调用

//    shared_ptr<Demo> Demo::d(new Demo); // private ctor is accepted 

我认为返回本地静态不起作用,请参见下面的示例对象被销毁两次

#include <iostream>

using namespace std;

class Demo {
public:
    static Demo & getInstance(){
        static Demo d;
        return d;
    }
    ~Demo(){
        cout << "Demo destroyed" << endl;
    }
};

void fun(){
    Demo l = Demo::getInstance();

}
int main()
{
    fun();
   cout << "Hello World" << endl; 
}

【讨论】:

  • 你知道在执行Demo l = Demo::getInstance();之后,你会得到一个你的单例的副本,这样你就不会再有单例了吗?
  • 设为Demo&amp; l = Demo::getInstance();(注意与号)。然后将Demo 的复制构造函数标记为已删除,如Demo(const Demo&amp;) = delete;,这样您的原始示例就不再编译了。能复制也不算单身吧?
【解决方案3】:

1 的一些 cmets 帮助讨论。静态变量会在应用退出时被销毁,所以我们在这个阶段不需要像上面提到的那样使用智能指针。

“如果多个线程同时尝试初始化同一个静态局部变量,则初始化只发生一次(使用 std::call_once 的任意函数可以获得类似的行为)。

注意:此功能的通常实现使用双重检查锁定模式的变体,这将已初始化的本地静态变量的运行时开销减少为单个非原子布尔比较。 (C++11 起)

块范围静态变量的析构函数在程序退出时调用,但前提是初始化成功进行。 "

【讨论】:

    猜你喜欢
    • 2011-10-17
    • 1970-01-01
    • 2020-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-17
    相关资源
    最近更新 更多