C++ 内存管理与单例模式剖析

目录

引言

一、堆上唯一对象:HeapOnly类

(一)设计思路

(二)代码实现

(三)使用示例及注意事项

二、栈上唯一对象:StackOnly类

(一)设计思路

(二)代码实现

(三)使用示例及注意事项

三、单例模式:饿汉模式与懒汉模式

(一)单例模式概述

(二)饿汉模式

(三)懒汉模式

(四)单例模式使用示例

总结


引言

在C++ 编程中,内存管理和设计模式是非常重要的两个方面。合理的内存管理能确保程序高效、稳定地运行,而设计模式则有助于构建更具可维护性、可扩展性的软件架构。今天,我们将深入探讨C++ 中关于内存管理的一些特殊类设计,以及经典的单例模式。

一、堆上唯一对象:HeapOnly类

(一)设计思路

HeapOnly 类的设计目的是强制对象只能在堆上创建。这是通过将构造函数设为私有来实现的。外部代码无法直接调用构造函数在栈上创建对象,也不能使用 static 关键字在静态存储区创建对象。

(二)代码实现

cpp 复制代码
class HeapOnly
{
public:
    static HeapOnly* CreateObj()
    {
        return new HeapOnly;
    }
private:
    HeapOnly()
    {
        //...
    }
    HeapOnly(const HeapOnly& hp) = delete;
    HeapOnly& operator=(const HeapOnly& hp) = delete;
};

这里,唯一能创建 HeapOnly 对象的方式是通过静态成员函数 CreateObj ,它使用 new 操作符在堆上分配内存并构造对象。同时,将拷贝构造函数和赋值运算符重载函数设为删除状态,防止对象被拷贝,进一步保证对象的唯一性和内存管理的安全性。

(三)使用示例及注意事项

cpp 复制代码
//int main()

//{
// //HeapOnly hp1; // 错误,无法在栈上创建
// //static HeapOnly hp2; // 错误,无法在静态存储区创建
// //HeapOnly* hp3 = new HeapOnly; // 错误,构造函数私有
// HeapOnly* hp3 = HeapOnly::CreateObj();
// HeapOnly copy(*hp3); // 错误,拷贝构造函数被删除
// return 0;
//}

在使用时,要严格遵循其设计规则,只能通过 CreateObj 获取对象指针,并且不能进行拷贝操作。

二、栈上唯一对象:StackOnly类

(一)设计思路

StackOnly 类与 HeapOnly 类相反,它的设计是为了确保对象只能在栈上创建。通过将 operator new 设为删除状态,禁止了使用 new 操作符在堆上创建对象。

(二)代码实现

cpp 复制代码
class StackOnly
{
public:
    static StackOnly CreateObj()
    {
        StackOnly st;
        return st;
    }
private:
    StackOnly()
    {
        //...
    }
    void* operator new(size_t size) = delete;

};

CreateObj 函数在函数内部的栈空间上创建 StackOnly 对象,并返回该对象的副本。由于 operator new 被删除,无法在堆上创建对象。

(三)使用示例及注意事项

cpp 复制代码
int main()
{
   //StackOnly hp1; // 错误,构造函数私有
    //static StackOnly hp2; // 错误,构造函数私有
    //StackOnly* hp3 = new StackOnly; // 错误,operator new被删除
    StackOnly hp3 = StackOnly::CreateObj();
    StackOnly copy(hp3); // 这里如果类没有合适的拷贝构造函数会有问题
    // new operator new + 构造
    // StackOnly* hp4 = new StackOnly(hp3); // 错误,operator new被删除
    return 0;

}

使用时要注意只能通过 CreateObj 来获取对象,并且要确保类的拷贝构造函数等符合需求,避免出现意外的错误。

三、单例模式:饿汉模式与懒汉模式

(一)单例模式概述

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。在很多场景下,比如日志记录器、数据库连接池等,只需要一个全局唯一的对象来进行管理和操作,单例模式就能很好地满足这种需求。

(二)饿汉模式

  1. 设计思路:饿汉模式在程序启动( main 函数之前)就创建单例对象。无论后续是否会用到这个单例对象,它都会被提前创建。

  2. 代码实现

cpp 复制代码
namespace hungry
{
    class Singleton
    {
    public:
        static Singleton& GetInstance()
        {
            return _sinst;
        }
        void func();
        void Add(const pair<string, string>& kv)
        {
            _dict[kv.first] = kv.second;
        }
        void Print()
        {
            for (auto& e : _dict)
            {
                cout << e.first << ":" << e.second << endl;
            }
            cout << endl;
        }
    private:
        Singleton()
        {
            // ...
        }
        Singleton(const Singleton& s) = delete;
        Singleton& operator=(const Singleton& s) = delete;
        map<string, string> _dict;
        static Singleton _sinst;
    };
    Singleton Singleton::_sinst;
    void Singleton::func()
    {
        _dict["xxx"] = "1111";
    }

}

这里, _sinst 是静态成员变量,在类外进行了定义和初始化。 GetInstance 函数返回这个唯一的单例对象的引用。

3. 优缺点

优点:实现简单,在程序启动时就创建好对象,不存在多线程并发创建对象的问题。

缺点:如果单例对象初始化内容很多,会影响程序的启动速度。并且当有多个互相依赖的单例类时,难以保证初始化顺序。

(三)懒汉模式

  1. 设计思路:懒汉模式是在第一次调用获取单例对象的函数时才创建对象。这样可以避免在程序启动时就创建不必要的对象,提高程序的启动效率。

  2. 代码实现

cpp 复制代码
namespace lazy
{
    class Singleton
    {
    public:
        static Singleton& GetInstance()
        {
            if (_psinst == nullptr)
            {
                _psinst = new Singleton;
            }
            return *_psinst;
        }

        static void DelInstance()
        {
            if (_psinst)
            {
                delete _psinst;
                _psinst = nullptr;
            }
        }

        void Add(const pair<string, string>& kv)
        {
            _dict[kv.first] = kv.second;
        }

        void Print()
        {
            for (auto& e : _dict)
            {
                cout << e.first << ":" << e.second << endl;
            }
            cout << endl;
        }

        class GC
        {
        public:
            ~GC()
            {
                lazy::Singleton::DelInstance();
            }
        }
    private:
        Singleton()
        {
            // ...
        }
        ~Singleton()
        {
            cout << "~Singleton()" << endl;
            FILE* fin = fopen("map.txt", "w");
            for (auto& e : _dict)
            {
                fputs(e.first.c_str(), fin);
                fputs(":", fin);
                fputs(e.second.c_str(), fin);
                fputs("\n", fin);
            }
        }
        Singleton(const Singleton& s) = delete;
        Singleton& operator=(const Singleton& s) = delete;
        map<string, string> _dict;
        static Singleton* _psinst;
        static GC _gc;
    };
    Singleton* Singleton::_psinst = nullptr;
    Singleton::GC Singleton::_gc;
}

这里通过 GetInstance 函数中的 if 判断来实现延迟创建对象。同时,定义了一个内部类 GC ,利用其析构函数在程序结束时释放单例对象,确保资源的正确回收。

3. 优缺点

优点:延迟创建对象,提高启动速度,并且可以在程序运行中根据需要释放单例对象,在一些特殊场景(如中途需要释放资源或程序结束时做持久化操作)下很有用。

缺点:在多线程环境下,如果不进行同步处理,可能会出现多个线程同时创建对象的问题,导致违反单例模式的原则。

(四)单例模式使用示例

cpp 复制代码
int main()
{
    cout << &lazy::Singleton::GetInstance() << endl;
    cout << &lazy::Singleton::GetInstance() << endl;
    cout << &lazy::Singleton::GetInstance() << endl;
    //Singleton copy(Singleton::GetInstance()); // 错误,拷贝构造函数被删除
    lazy::Singleton::GetInstance().Add({ "xxx", "111" });
    lazy::Singleton::GetInstance().Add({ "yyy", "222" });
    lazy::Singleton::GetInstance().Add({ "zzz", "333" });
    lazy::Singleton::GetInstance().Add({ "abc", "333" });
    lazy::Singleton::GetInstance().Print();
    //lazy::Singleton::DelInstance();
    lazy::Singleton::GetInstance().Add({ "abc", "444" });
    lazy::Singleton::GetInstance().Print();
    //lazy::Singleton::DelInstance();
    return 0;
}

在 main 函数中,多次调用 GetInstance 获取单例对象,并对其进行操作,验证了单例对象的唯一性和可操作性。

总结

通过对 HeapOnly 类、 StackOnly 类以及单例模式的饿汉模式和懒汉模式的深入剖析,我们了解了C++ 中一些特殊的内存管理方式和经典的设计模式。这些知识在实际编程中非常实用,合理运用它们可以让我们的程序在内存管理上更加合理,架构上更加清晰和稳定。在具体应用时,要根据实际需求和场景选择合适的方案,同时注意避免出现内存泄漏、对象创建错误等问题。

相关推荐
小贾要学习14 分钟前
【C++】stack,queue和priority_queue(优先级队列)
java·c++·rpc
敲代码的瓦龙1 小时前
C++?继承!!!
c语言·开发语言·c++·windows·后端·算法
恒者走天下2 小时前
让学习回归到技术上来(技术 !=== 死记硬背)
c++
linux开发之路2 小时前
【备战秋招】C++音视频开发经典面试题整理
c++·ffmpeg·音视频·rtmp·音视频编解码
小吴同学·3 小时前
OPC Client第5讲(wxwidgets):初始界面的事件处理;按照配置文件初始化界面的内容
c++·wxwidgets
虾球xz4 小时前
游戏引擎学习第314天:将精灵拆分成多个层
linux·c++·学习·游戏引擎
会开花的二叉树4 小时前
vector的实现
数据结构·c++·算法·stl
序属秋秋秋4 小时前
《数据结构初阶》【番外篇:快速排序的前世今生】
c语言·数据结构·c++·笔记·leetcode·排序算法
CodeWithMe5 小时前
【C/C++】红黑树学习笔记
c语言·c++·学习
天赐细莲5 小时前
(C++17) 未捕获异常 uncaught_exceptions
开发语言·c++