题 任何人都可以在c ++中为我提供Singleton样本吗?


我按照以下方式编写单例c ++:

class A {
    private:
        static A* m_pA;
        A();
        virtual ~A();

    public:
        static A* GetInstance();
        static void FreeInstance();

        void WORK1();
        void WORK2();
        void WORK3();
    }
}

A* A::GetInstance() {
    if (m_pA == NULL)
        m_pA = new A();
    return m_pA;
}

A::~A() {
    FreeInstance()  // Can I write this? are there any potential error?
}

void A::FreeInstance() {
    delete m_pA;
    m_pA = NULL;
}

谢谢! Evan Teran和sep61.myopenid.com的回答是对的,非常好! 我的方式是错的,我希望任何人写这样的代码都可以避免我的愚蠢错误。

我的项目中的单例A有一个智能指针向量,另一个线程也可以编辑这个向量,所以当应用程序关闭时,即使我添加了很多CMutex,它总是变得不稳定。多线程错误+单身错误浪费了我一天。

// ------------------------------------------------ ----------- 一个新的单例,如果您认为以下示例中存在任何问题,欢迎您进行编辑:

class A {
    private:
        static A* m_pA;
        explicit A();
        void A(const A& a);
        void A(A &a);
        const A& operator=(const A& a);
        virtual ~A();

    public:
        static A* GetInstance();
        static void FreeInstance();

        void WORK1();
        void WORK2();
        void WORK3();
    }
}

A* A::GetInstance() {
    if (m_pA == NULL){
        static A self;
        m_pA = &self;
    }
    return m_pA;
}

A::~A() {
}


35
2017-11-07 01:12


起源


本文中可以找到有关如何正确实现单例的有趣讨论,以及C ++中的线程安全性: aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf
可能重复 C ++ Singleton设计模式。该 stackoverflow.com/q/1008019/52074 是更好的,因为它多10倍的upvotes和问题更新与C ++ 11和 问题/答案受到社区的保护/维护。 - Trevor Boyd Smith


答案:


您可以通过使用如下静态对象来避免需要删除它:

if(m_pA == 0) {
    static A static_instance;
    m_pA = &static_instance;
}

12
2017-11-07 01:16



注意:如果您使用多线程,那么您应该非常小心这样做。看到 stackoverflow.com/questions/246564/... 为什么一般来说这是一个非常糟糕的主意。 - hazzen
非常好的一点,这就是为什么当使用多个线程时,应该有互斥或其他一些同步原语。 - Evan Teran
只是不要尝试使用双重锁定来实现它,因为这不能在C ++中正常工作(有关详细信息,请参阅google)。 - Martin York
最好的方法是解耦引用和创建,并让主线程在启动任何可能引用单例的线程之前创建所有单例。顺便说一下,返回引用在大多数情况下优于返回指针... - Andreas Magnusson
aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf 是C ++中双重检查锁定问题的一个很好的参考。 - xtofl


为什么每个人都希望将单例作为指针返回?
返回它作为参考似乎更合乎逻辑!

你永远不能手动释放单身人士。你怎么知道谁在提到单身人士?如果您不知道(或不能保证)没有人有引用(在您的情况下通过指针),那么您没有业务释放该对象。

在函数方法中使用static。
这保证了它只被创建和销毁一次。它还为您提供免费的延迟初始化。

class S
{
    public:
        static S& getInstance()
        {
            static S    instance;
            return instance;
        }
    private:
        S() {}
        S(S const&);              // Don't Implement.
        void operator=(S const&); // Don't implement
 };

请注意,您还需要将构造函数设为私有。 还要确保覆盖默认的复制构造函数和赋值运算符,以便不能复制单例(否则它不会是单例)。

另请阅读:

确保您使用单身人员是出于正确的原因。

虽然在一般情况下技术上不是线程安全的,但请参阅:
C ++函数中静态变量的生命周期是多少?

海湾合作委员会有一个明确的补丁来补偿这个:
http://gcc.gnu.org/ml/gcc-patches/2004-09/msg00265.html


189
2017-11-07 02:51



引用的另一个好处是不可能这样做:'delete A :: getInstance()',这可以通过指针实现。 - yoav.aviram
我想每个人都想过早地进行优化,并且当他们做指针时他们感觉自己处于控制之中。但是,即使是'static S instance'子句也要编译为线程安全的构造!是吗? - xtofl
不应该是赋值运算符的声明 S& operator=(S const&);? - kol
我知道为什么单例的赋值运算符是私有的,为什么它没有实现。但是我认为如果使用更常见的赋值运算符版本(即返回对受让人的引用的那个),代码会更具可读性: en.wikipedia.org/wiki/... - kol
@kol有一个完全合理的观点。当然,你不能链接一个单身人士。好吧,如果你将作业链接到同一个对象,你可以。无论如何,我不希望因此而看到一个奇怪的API。它更像是为每一组定义自己的自定义整数类型 int您想要的具有不同的逻辑范围。你知道,因为我的“小时”计数器不能超过23,我为什么要它能够达到20亿?我很高兴你“不要盲目地复制和粘贴代码”,但另一方面,这并不是一个单凭顽固坚持令人惊讶的态度的好理由。 - Lightness Races in Orbit


C ++中的单例可以用这种方式编写:

static A* A::GetInstance() {
    static A sin;
    return &sin;
}

4
2017-11-07 01:22





只是不要忘记将复制构造函数和赋值运算符设为私有。


2
2017-11-07 01:24





我认为没有任何理由不写这条线。您的析构函数方法不是静态的,并且您的单例实例不会以这种方式被破坏。我不认为析构函数是必要的,如果你需要使用你已经创建的静态方法FreeInstance()来清理对象。

除此之外,你创造单身的方式与我创造单身的方式大致相同。


1
2017-11-07 01:20





经过一段时间对Meyers风格的单身人士充满热情(使用本地静态对象,如前面的一些答案),我完全厌倦了复杂应用程序中的终身管理问题。

我倾向于发现你最终在应用程序的初始化过程中故意引用“实例”方法,以确保它们是在你想要的时候创建的,然后因为不可预测而用下拉式播放各种游戏(或者至少非常复杂和有些隐藏的秩序,其中的东西被摧毁。

YMMV当然,它取决于单身人士本身的性质,但很多关于聪明的单身人士(和围绕聪明的线程/锁定问题)的胡扯被IMO高估了。


1
2017-11-07 11:41





如果您阅读“现代C ++设计”,您会发现单例设计可能比返回静态变量复杂得多。


1
2017-11-07 11:56





只要您可以回答以下问题,此实现就可以了:

  1. 你知道什么时候会创建对象(如果你使用的是静态对象而不是new?你有main()吗?)

  2. 你是否有单身人士有任何依赖,可能在创建时尚未准备好?如果您使用静态对象而不是新对象,那么此时已初始化了哪些库?您的对象在构造函数中可能需要它们的作用是什么?

  3. 什么时候会被删除?

使用new()更安全,因为您可以控制创建和删除对象的位置和时间。但是你需要明确删除它,系统中没有人知道何时这样做。如果有意义,你可以使用atexit()。

在方法中使用静态对象意味着确实不知道何时创建或删除它。您也可以在命名空间中使用全局静态对象并完全避免使用getInstance() - 它不会增加太多。

如果你确实使用线程,那么你就麻烦大了。由于以下原因,在C ++中创建可用的线程安全单例实际上是不可能的:

  1. getInstance中的永久锁定很重 - 每个getInstance()都有一个完整的上下文切换
  2. 由于编译器优化和缓存/弱内存模型,双重检查锁失败,实现起来非常棘手,而且无法测试。我不会尝试在真实系统中进行此操作,除非您非常了解您的体系结构并希望它不可移植

这些可以很容易地用Google搜索,但这里是弱内存模型的一个很好的链接: http://ridiculousfish.com/blog/archives/2007/02/17/barrier

一种解决方案是使用锁定,但要求用户缓存从getInctance()获取的指针,并为getInstance()做好准备。

另一种解决方案是让用户自己处理线程安全。

另一种解决方案是使用具有简单锁定的函数,并将其替换为另一个函数,而无需在调用new()后锁定和检查。这有效,但完全实现很复杂。


0
2017-11-07 12:10