目录
引言
一、堆上唯一对象:HeapOnly类
(一)设计思路
(二)代码实现
(三)使用示例及注意事项
二、栈上唯一对象:StackOnly类
(一)设计思路
(二)代码实现
(三)使用示例及注意事项
三、单例模式:饿汉模式与懒汉模式
(一)单例模式概述
(二)饿汉模式
(三)懒汉模式
(四)单例模式使用示例
总结
引言
在C++ 编程中,内存管理和设计模式是非常重要的两个方面。合理的内存管理能确保程序高效、稳定地运行,而设计模式则有助于构建更具可维护性、可扩展性的软件架构。今天,我们将深入探讨C++ 中关于内存管理的一些特殊类设计,以及经典的单例模式。
一、堆上唯一对象:HeapOnly类
(一)设计思路
HeapOnly 类的设计目的是强制对象只能在堆上创建。这是通过将构造函数设为私有来实现的。外部代码无法直接调用构造函数在栈上创建对象,也不能使用 static 关键字在静态存储区创建对象。
(二)代码实现
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 操作符在堆上分配内存并构造对象。同时,将拷贝构造函数和赋值运算符重载函数设为删除状态,防止对象被拷贝,进一步保证对象的唯一性和内存管理的安全性。
(三)使用示例及注意事项
//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 操作符在堆上创建对象。
(二)代码实现
class StackOnly
{
public:
static StackOnly CreateObj()
{
StackOnly st;
return st;
}
private:
StackOnly()
{
//...
}
void* operator new(size_t size) = delete;
};
CreateObj 函数在函数内部的栈空间上创建 StackOnly 对象,并返回该对象的副本。由于 operator new 被删除,无法在堆上创建对象。
(三)使用示例及注意事项
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. 代码实现
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. 代码实现
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. 优缺点
优点:延迟创建对象,提高启动速度,并且可以在程序运行中根据需要释放单例对象,在一些特殊场景(如中途需要释放资源或程序结束时做持久化操作)下很有用。
缺点:在多线程环境下,如果不进行同步处理,可能会出现多个线程同时创建对象的问题,导致违反单例模式的原则。
(四)单例模式使用示例
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++ 中一些特殊的内存管理方式和经典的设计模式。这些知识在实际编程中非常实用,合理运用它们可以让我们的程序在内存管理上更加合理,架构上更加清晰和稳定。在具体应用时,要根据实际需求和场景选择合适的方案,同时注意避免出现内存泄漏、对象创建错误等问题。