【问题标题】:Singleton: is there a memory leak?Singleton:有内存泄漏吗?
【发布时间】:2015-06-03 02:40:47
【问题描述】:

这是一个简单的单例:

class Singleton
{
    Singleton();
    virtual ~Singleton();

    Singleton * Singleton::getInstance() 
    {
        static Singleton * instance;

        if (!instance) {
            instance = new Singleton();
        };
        return instance;
    };
}

主代码第一次调用Singleton::getInstance()->someMethod()时,类不是实例化了两次吗?会不会有内存泄露?

我之所以问,是因为 Visual Leak Detector 检测到内存泄漏与 new Singleton()

【问题讨论】:

  • 这不是单例。 getInstance() 不是静态成员,因此您需要至少一个 Singleton 类的对象来调用 getInstance(),之后您有 两个 Singleton 对象。我不知道在哪里声明了实例,但似乎没有人delete 它曾经。所以——是的,你有内存泄漏。
  • 你应该在这里返回一个引用,static Singleton&,并声明static Singleton instance; 不需要额外的检查等,它也是从 C++11 开始的线程安全的。
  • @vsoftco:你有一个额外的&。但你的观点非常好。
  • @LightnessRacesinOrbit 刚刚删除了它 :)
  • @vsoftco:我的答案偷了你的想法

标签: c++ memory-leaks singleton


【解决方案1】:

主代码第一次调用Singleton::getInstance()->someMethod()时,类不是实例化了两次吗?

没有。调用Singleton 的静态成员不会实例化Singleton,因此此处存在的唯一Singleton 实例是您使用new 创建的实例。而且你只创建一次,因为一旦instance 指向它,你就再也不会调用new

但是,您在这里遇到的一个问题是您未能使getInstance 成为静态成员函数。我认为这是一个错字/疏忽,否则,您的程序甚至无法编译。事实上,即使作为非静态成员,声明也是格式错误的。此外,构造函数应该是 private 以强制执行只有 getInstance 可以实例化类型的概念。

会不会有内存泄漏?

是的,这就是 Leak Detector 报告的内容。然而,它是最小的:问题是在你的程序关闭之前,delete 那个单例实例没有任何东西。

坦率地说,我不会担心。这可能是可以接受泄漏的罕见情况之一,主要是因为不仅仅是“泄漏”,它只是在进程关闭时取消分配的一次性失败。

但是,如果您完全摆脱指针,那么您可以同时避免这两个问题,因为您的程序所做的最后一件事就是销毁静态存储持续时间的对象:

#include <iostream>

class Singleton
{
public:
    ~Singleton()  { std::cout << "destruction!\n"; }

    static Singleton& getInstance() 
    {
        static Singleton instance;
        return instance;
    }

    void foo() { std::cout << "foo!\n"; }

private:
    Singleton() { std::cout << "construction!\n"; }
};

int main()
{
    Singleton::getInstance().foo();
}

// Output:
//   construction!
//   foo!
//   destruction!

(live demo)

甚至不需要指针!

这具有额外的好处,即整个函数本质上是线程安全的,至少在 C++11 中是这样。

【讨论】:

  • "调用静态成员..." - getInstance() 应该是静态的吗?
  • @Vladon 啊,应该。尽管语法不正确,但这似乎是明确的意图。
  • 请注意,在 Visual Studio 的情况下,它只是 Visual Studio 2015 的线程安全。
  • @KABoissonneault:lolsrsly?并不是我感到惊讶。无论如何,我会让读者了解他的实现实际支持的 C++11 的哪些部分。
  • 指针版本的另一个问题是它的析构函数永远不会被调用,这可能是也可能不是问题。
【解决方案2】:

@LightnessRacesInOrbit 的答案是正确的,而且切中要害。由于您的问题以某种方式致力于实现单例时的最佳实践(您应该尽量避免顺便说一句),我会给您我的看法。您可以使用称为Curiously Recurring Template Pattern 的东西来创建Singleton 基类,它将您想要创建单例的类作为模板参数。一旦你正确地做到了这一点,创建单例就像在公园里散步一样。只需从Singleton&lt;Foo&gt; 派生Foo 并将Foo::Foo()Foo::~Foo() 设为私有即可。这是我使用的代码,带有 cmets,live on Coliru

// Singleton pattern via CRTP (curiously recurring template pattern)
// thread safe in C++11 and later

#include <iostream>
#include <type_traits>

// generic Singleton via CRTP
template <typename T>
class Singleton
{
protected:
    Singleton(const Singleton&) = delete; // to prevent CASE 3
    Singleton& operator=(const Singleton&) = delete; // to prevent CASE 4
    Singleton() noexcept = default; // to allow creation of Singleton<Foo>
    // by the derived class Foo, since otherwise the (deleted)
    // copy constructor prevents the compiler from generating
    // a default constructor;
    // declared protected to prevent CASE 5
public:
    static T& get_instance()
    noexcept(std::is_nothrow_constructible<T>::value)
    {
        static T instance;
        return instance;
    }
    // thread local instance
    static thread_local T& get_thread_local_instance()
    noexcept(std::is_nothrow_constructible<T>::value)
    {
        static T instance;
        return instance;
    }
};

// specific Singleton instance
// use const if you want a const instance returned
// make the constructor and destructor private
class Foo: public Singleton</*const*/ Foo>
{
    // so Singleton<Foo> can access the constructor and destructor of Foo
    friend class Singleton<Foo>;
    Foo() // to prevent CASE 1
    {
        std::cout << "Foo::Foo() private constructor" << std::endl;
    }
    // OK to be private, since Singleton<Foo> is a friend and can invoke it
    ~Foo() // to prevent CASE 2
    {
        std::cout << "Foo::~Foo() private destructor" << std::endl;
    }
public:
    void say_hello()
    {
        std::cout << "\t Hello from Singleton" << std::endl;
    }
};

int main()
{
    Foo& sFoo = Foo::get_instance();
    sFoo.say_hello();

    Foo& sAnotherFoo = Foo::get_instance(); // OK, get the same instance
    sAnotherFoo.say_hello();

    Foo& sYetAnotherFoo = sFoo; // still OK, get the same instance
    sYetAnotherFoo.say_hello();

    thread_local Foo& stlFoo = Foo::get_thread_local_instance(); // thread local instance
    stlFoo.say_hello();

    // CASE 1: error: 'Foo::Foo()' is private
    // Foo foo;
    // Foo* psFoo = new Foo;

    // CASE 2: error: 'Foo::~Foo()' is private
    Foo* psFoo = &Foo::get_instance(); // can get pointer
    psFoo->say_hello();
    // delete psFoo; // cannot delete it

    // CASE 3: error: use of deleted function 'Foo::Foo(const Foo&)'
    // Foo foo(sFoo);

    // CASE 4: error: use of deleted function 'Foo& Foo::operator=(const Foo&)'
    // sFoo = sAnotherFoo;

    // CASE 5: error: 'Singleton<T>::Singleton() [with T = Foo]' is protected
    // Singleton<Foo> direct_sFoo;
}

【讨论】:

    【解决方案3】:

    new Singleton() 没有匹配的delete,所以是的,您正在泄漏资源。

    程序关闭时你会取回内存,但程序结束时并非所有资源都返回。

    您可以通过将实例设为std::auto_ptrstd::unique_ptr 来修复它。或者干脆不使用指针。

    【讨论】:

    • 请不要推荐使用已弃用的对象。
    • 程序结束时不会返回哪些资源?在哪个系统上?
    • @KABoissonneault 这不是 C++11 问题。
    • @QuestionC:我们现在是 2015 年,所以默认情况下这是一个 C++11/C++14 问题。如果只能使用 C++03,则需要说明。该技术已有十二年(根据某些定义,十七)!无论如何,std::auto_ptr 被弃用是有充分理由的,因此向语言新手推荐使用它是不负责任的。
    • @QuestionC:你到底为什么要用单例实现它?
    猜你喜欢
    • 1970-01-01
    • 2019-12-21
    • 1970-01-01
    • 2019-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多