关于C++单例模式内存释放问题的一点点总结

目录


写在最前面

  网络上有很多关于C++单例模式的帖子,其中不乏精品之作。本篇文字在吸收了精华之余,仅作了个人的一点点总结。
  通过new出一个对象来实现的单例,不论单例是通过饿汉方式,还是懒汉方式来实现,都面临一个问题,即new出来的对象由谁释放,何时释放,怎么释放 ?简单的实现可以参考C++ 单例模式
  如果对象没有被释放,在运行期间可能会存在内存泄露问题。有人可能会说,在进程结束时,操作系统会进行必要的清理工作,包括释放进程的所有堆栈等信息,即使存在内存泄露,操作系统也会收回的;且对于单例来讲,进程运行期间仅有一个对象实例,而且该实例有可能根本就没有进行内存的申请操作,不释放实例所占内存,对进程的运行也不会造成影响。这么说好像很有道理的样子,既然操作系统会清理一切后续工作,那么我们还有必要进行内存释放工作吗?
  

正文

  闲话少叙,言归正传。作为一个非著名的程序猿,我和大多数同类一样,对代码有着不一般的情结,且有强迫症。成对的使用new和delete是程序猿们最基础的素养。这么看来,单例对象的释放必须要在代码中体现出来。

方式一:由程序猿在程序结束之前,通过调用delete来释放

很自然的,可能会有部分猿/媴们[其实就是我啦^-^]想到,把释放工作交给析构函数来处理不就行了。想法是不错,代码要怎么写呐?可能如下:

~dtor()
{
   delete instance;
}

可惜的是,一:new出来的对象,必须用与之对应的delete显示的来释放,程序并不会自动调用析构函数来析构new出来的对象;二:在delete的时候会调用析构函数,析构函数中又调用了delete,然后又调用了析构函数……这样就进入了一个无限的循环之中。

可能的代码:

int main(int argc, char ** argv)
{
    //...

    delete Singleton::get_instance();
    //...
}

valgrind检测结果
所有内存都被释放了
通过valgrind工具,我们可以看到,所有内存都被释放了。这种处理完成了任务,好像无可厚非。但是,大多数情况下,这条语句会被遗忘,如果程序中存在多个单例,也很容易将某个对象的释放操作遗漏。

方式二:通过C标准库的atexit()函数注册释放函数

  atexit()函数可以用来注册终止函数。如果打算在main()结束后执行某些操作,可以使用该函数来注册相关函数。

可能的代码:

void del_singleton_01()
{
    if (Singleton::get_instance())
    {
        delete Singleton::get_instance();
    }
}

int main(int argc, char **argv)
{
    // ...
    atexit(del_singleton_01);
    // ...
}

valgrind检测结果
所有内存都被释放了
  标准规定atexit()至少可以注册32个终止函数,如果系统中有多个单例,我们可能要注册多个函数,或者在同一个终止函数中释放所有单例对象。但是方式一中的问题依然存在。必须由程序猿/媴手工注册,且有可能遗漏某个对象。

方式三:由单例类提供释放接口

  本方式由单例类提供一个释放对象的函数,在该函数内部进行对象的释放操作。其本质与方式一并无太大差别,同样的继承了方式一的缺点。
可能的代码:

class Singleton {
public:
    // ...
    void del_object() {
        if (instance) {
            delete instance;
            instance = 0;
        }
    }
    // ...
};

int main(int argc, char ** argv)
{
    // ...
    Singleton::get_instance()->del_object();
    // ...
}

  虽然这是一种可行的释放对象方式,但是这种方式并没有明显的优点。这不是我们想要的方案。

方式四:让操作系统自动释放

  我们知道,进程结束时,静态对象的生命周期随之结束,其析构函数会被调用来释放对象。因此,我们可以利用这一特性,在单例类中声明一个内嵌类,该类的析构函数专门用来释放new出来的单例对象,并声明一个该类类型的static对象。

可能的代码:

class Singleton {
public:
    // ...
private:
    // ...
    static Singleton * instance;
    class GarbageCollector {
    public:
        ~GarbageCollector() {
            if (Singleton::instance) {
                delete Singleton::instance;
                Singleton::instance = 0;
            }
        }
    };
    GarbageCollector gc;
};

// 定义
Singleton::GarbargeCollector Singleton::gc;
// ...

valgrind检测结果:
所有内存都被释放了
好了,我们可以像之前一样使用单例了,不需要再关心对象的释放问题。进程结束时,操作系统会帮我们去释放的。

写在最后面

  之所以要进行内存的释放,是因为在单例的实现过程中,我们使用了new来创建对象。如果在实现过程中,不使用new,而是使用静态[局部]对象的方式,就不存在释放对象的问题了。
可能的关键代码:

class Singleton {
    // ...
    static Singleton instance;
    // ...
};

// ...
Singleton Singleton::instance;
// ...

或者

class Singleton {
public:
    Singleton & get_instance();
    // ...
};

Singleton & Singleton::get_instance()
{
    static Singleton instance;
    return instance;
}

猜你喜欢

转载自blog.csdn.net/linuxwuj/article/details/81187564