单例模式析构函数 C++

11

我有这个单例模式,它运行良好。但当我使用valgrind执行程序以检查内存泄漏时,似乎实例从未被销毁。

我的错误在哪里?

头文件

class Stopwords {   
    private:
        static Stopwords* instance;
        std::map<std::string,short> diccionario;

    private: 
    Stopwords();

    public:
        ~Stopwords();

    public:
    static Stopwords* getInstance();
    std::map<std::string,short> getMap();
};

.cpp

Stopwords* Stopwords::instance = NULL;

Stopwords::Stopwords() {
    diccionario = map<string,short>();

    char nombre_archivo[] = "stopwords/stopwords.txt";
    ifstream archivo;
    archivo.open(nombre_archivo);
    string stopword;
    while(getline(archivo,stopword,',')) {
    diccionario[stopword] = 1;
    }
    archivo.close();
}

Stopwords::~Stopwords() {
    delete instance;
}


Stopwords* Stopwords::getInstance() {

    if (instance == NULL) {
       instance = new Stopwords ();
    }
    return instance;
}

map<string,short> Stopwords::getMap(){
    return diccionario;
}

这段内容并不相关,但在初始化过程中,我从文件中读取了一些单词,并将它们保存在一个映射实例中。

谢谢

5个回答

23
Stopwords::~Stopwords() {
    delete instance;
}

这是类实例的析构函数。你可能打算在程序结束时调用此函数,就像一种“静态”析构函数,但这不是这个函数的目的。
因此,你的Stopwords实例的析构函数启动了Stopwords实例的销毁;这里有一个无限循环,你从未进入。如果你进入了这个循环,那么程序很可能会崩溃。
有一种更简单的方法来创建单例模式:不要将实例作为静态类成员手动分配,而是将其保持为静态函数变量。C++会帮你管理创建和销毁它。
class Stopwords {   
public:
    static Stopwords &getInstance() {
        static Stopwords instance;
        return instance;
    }

    ~Stopwords();
    std::map<std::string,short> getMap();

private:
    Stopwords();
    std::map<std::string,short> diccionario;
};

此外,您应该将不需要修改类的成员函数标记为const

std::map<std::string,short> getMap() const;

啊,我明白了,它是静态的。我没看到。 - Ehsan Ab
一个重要的警告:Stopwords实例是按需创建的!这意味着它的构造函数只会在第一次调用getInstance()时被调用(一次)。你必须记住这种懒惰实例化行为,以防你从并发上下文(多线程)调用getInstance()的情况。为了避免这个问题,将“static Stopwords instance;”移到getInstance()定义之外,并放入全局上下文(*.cpp)中 - 这样它将在主过程之前被创建(不是懒惰地)。 - Zuzu Corneliu
@bames53 哦,好的知道了。但这也意味着在每次调用getInstance()时都必须获取并释放一个锁。我认为这是不值得的运行时开销。 - Zuzu Corneliu
1
关于使用 static T *instancestatic T instance 的注意事项。如果 T 本身很大,则前者可能更可取,因为它执行堆分配而不是静态数据分配。这使得可执行映像大小更小,在数据段中仅保留 sizeof(T *) 字节,而不是 sizeof(T) 字节。 - Josh Greifer
1
@Rodrigo 为了防止这种情况,你可以删除复制构造函数。 - bames53
显示剩余7条评论

14
问题在于你从外部没有手动调用实例指针的析构函数或删除操作。因此,在析构函数内部的删除操作将永远不会执行,这意味着析构函数将永远不会得到执行,进而导致删除操作永远不会执行...... 你看到自己做了什么了吗?你的析构函数间接地调用了自身,这将导致问题。而且你从不从外部调用它,因此它根本不会被调用 - 幸运的是。
你应该改变单例模式的实现方式,也许使用 Meyers 单例(查一下),或者更好的方式是不使用单例。像这种数据源的情况下,单例模式存在太多弱点需要处理。

3
他也可以使用std::unique_ptr<Stopwords>而不是一个裸指针。 - odedsh
另外,更好的做法是只引用 instance 而不是将它作为指针,在 getInstance() 中返回 &instance - user1551592
@odedsh 这将是一个可能的解决方案,因为 unique_ptr 析构函数将从外部隐式调用。 - Arne Mertz

2
  1. 使用new为实例分配内存,因此实例将一直存在,直到调用delete。
  2. 在类的实例即将被销毁时,将调用析构函数。
  3. 没有什么能够销毁(使用delete)您的实例,只有析构函数本身。

因此,您的实例永远不会被销毁。

通常情况下,当您使用单例模式时,您不需要在程序完成之前将其销毁。为什么您需要这样做呢?

如果您不需要,请使用static关键字来明确表明它在程序结束之前一直存在。

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

-1
你可以通过将实例作为函数静态 std::unique_ptr 放在实例获取器内部来在 C++ 中实现单例,而不是作为类静态变量。这样可以确保在程序完成时调用析构函数,并允许您创建一个通过指向抽象基类的指针进行多态访问的实例。

-7
在你的析构函数中,你这样做:
Stopwords::~Stopwords() {
    delete instance;
}

我建议您添加以下内容:
Stopwords::~Stopwords() {
    delete instance;
    instance = 0;
}

这个调用确保指针不仅从内存中删除,而且指向空。当删除指针时,您需要确保它不再指向任何内容,否则可能会导致内存泄漏。


2
如果析构函数实际上不是从外部调用的话,这样做并没有任何帮助。 - Arne Mertz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接