39

我按以下方式编写了一个单例 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,它总是变得不稳定。多线程错误 + 单例错误浪费了我 1 天。

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

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() {
}
4

10 回答 10

206

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

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

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

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++ 函数中静态变量的生命周期是多少?

GCC 有一个明确的补丁来弥补这一点:http:
//gcc.gnu.org/ml/gcc-patches/2004-09/msg00265.html

于 2008-11-07T02:51:26.193 回答
13

您可以避免使用这样的静态对象来删除它:

if(m_pA == 0) {
    static A static_instance;
    m_pA = &static_instance;
}
于 2008-11-07T01:16:56.947 回答
4

C++ 中的单例可以这样写:

static A* A::GetInstance() {
    static A sin;
    return &sin;
}
于 2008-11-07T01:22:30.010 回答
3

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

于 2008-11-07T01:24:40.237 回答
1

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

除此之外,您创建单身人士的方式与我创建单身人士的方式大致相同。

于 2008-11-07T01:20:24.507 回答
1

在对 Meyers 风格的单例(在之前的一些答案中使用本地静态对象)充满热情之后,我完全厌倦了复杂应用程序中的生命周期管理问题。

我倾向于发现您最终会在应用程序初始化的早期故意引用“实例”方法,以确保在您需要时创建它们,然后由于不可预测(或至少非常复杂且有些隐藏)事物被破坏的顺序。

当然是 YMMV,这在一定程度上取决于单例本身的性质,但是关于聪明的单例(以及围绕聪明的线程/锁定问题)的许多华夫饼被高估了 IMO。

于 2008-11-07T11:41:25.223 回答
1

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

于 2008-11-07T11:56:00.373 回答
0

有一个很棒的 C++ 库,ACE,它基于模式。有很多关于不同类型模式的文档,所以看看他们的工作: http ://www.cs.wustl.edu/~schmidt/ACE.html

于 2008-11-07T11:10:57.403 回答
0

只要你能回答这些问题,这个实现就很好:

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

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

  3. 什么时候会被删除?

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

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

如果你确实使用线程,那么你就有大麻烦了。在 C++ 中创建可用的线程安全单例几乎是不可能的,因为:

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

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

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

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

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

于 2008-11-07T12:10:25.070 回答
0
//! @file singleton.h
//!
//! @brief Variadic template to make a singleton out of an ordinary type.
//!
//! This template makes a singleton out of a type without a default
//! constructor.

#ifndef SINGLETON_H
#define SINGLETON_H

#include <stdexcept>

template <typename C, typename ...Args>
class singleton
{
private:
  singleton() = default;
  static C* m_instance;

public:
  singleton(const singleton&) = delete;
  singleton& operator=(const singleton&) = delete;
  singleton(singleton&&) = delete;
  singleton& operator=(singleton&&) = delete;

  ~singleton()
  {
    delete m_instance;
    m_instance = nullptr;
  }

  static C& create(Args...args)
  {
    if (m_instance != nullptr)
      {
    delete m_instance;
    m_instance = nullptr;
      }
    m_instance = new C(args...);
    return *m_instance;
  }

  static C& instance()
  {
    if (m_instance == nullptr)
      throw std::logic_error(
        "singleton<>::create(...) must precede singleton<>::instance()");
    return *m_instance;
  }
};

template <typename C, typename ...Args>
C* singleton<C, Args...>::m_instance = nullptr;

#endif // SINGLETON_H
于 2019-01-09T21:01:36.110 回答