1.不能被拷贝的类
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
1.1 C++98的实现方式
将拷贝构造和赋值运算符重载设置为私有:
cpp
#include <iostream>
using namespace std;
class Copyban {
public:
Copyban(int a)
:_a(a)
{}
private:
Copyban(const Copyban& d);
Copyban& operator=(const Copyban& d);
private:
int _a;
};
int main()
{
Copyban c1(0);
//Copyban c2 = c1;//error
//Copyban c3(c1);//error
return 0;
}
1.2 C++11实现方式
C++11扩展delete的用法,delete除了释放new申请的资源 外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
cpp
#include <iostream>
using namespace std;
class Copyban {
public:
Copyban(int a)
:_a(a)
{}
private:
Copyban(const Copyban& d) = delete;
Copyban& operator=(const Copyban& d) = delete;
public:
int _a;
};
int main()
{
Copyban c1(1);
//Copyban c2 = c1;//error
//Copyban c3(c1);//error
return 0;
}
2.只能在堆上创建对象
2.1 方式1
因栈对象销毁需调用析构函数,所以将析构函数设为私有(达到禁止在栈上创建对象的目标),并提供自定义 Destroy 成员函数供堆对象释放。
cpp
#include <iostream>
using namespace std;
class HeapOnly {
public:
HeapOnly(int a)
:_a(a)
{}
void Destroy() {
delete this;
}
private:
~HeapOnly() {}
private:
int _a;
};
int main() {
HeapOnly* h1 = new HeapOnly(2);
h1->Destroy();
//HeapOnly h2;//error:不存在析构函数
return 0;
}
2.2 方式2
将类的构造函数私有,拷贝构造声明成私有,防止别人调用拷贝在栈上生成对象。提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
cpp
#include <iostream>
using namespace std;
class HeapOnly {
public:
// 静态成员函数:作为创建堆上对象的唯一入口
static HeapOnly* createObj(int a) {
return new HeapOnly(a);
}
private:
// 将构造函数设为私有:禁止类外部直接创建对象
// 确保只能通过类提供的接口(CreateObj)创建对象
HeapOnly(int a)
:_a(a)
{}
// 禁用operator new:阻止通过new在栈上创建对象
HeapOnly(const HeapOnly& h) = delete;
HeapOnly& operator=(const HeapOnly& h) = delete;
private:
int _a;
};
int main() {
HeapOnly* h1 = HeapOnly::createObj(2);
HeapOnly* h0(h1);
HeapOnly* h4 = h0;
//无法直接调用私有构造函数 HeapOnly(int a) 来创建对象
HeapOnly* h2 = new HeapOnly(a);//error
HeapOnly h3(2);//error
return 0;
}
3.只能在栈上创建的类
将构造函数设为私有,仅允许通过静态成员函数在栈上创建对象,并禁用 operator new 以阻止堆上创建,同时允许拷贝构造以支持栈上对象的复制。
cpp
#include <iostream>
using namespace std;
class StackOnly {
public:
static StackOnly creatObj(int a) {
StackOnly s(a);
return s;
}
private:
// 将构造函数设为私有:禁止类外部直接创建对象
// 确保只能通过类提供的接口(CreateObj)创建对象
StackOnly(int a)
:_a(a)
{}
// 禁用operator new:阻止通过new在堆上创建对象
// 当尝试使用new StackOnly时,编译器会因该函数被删除而报错
void* operator new(size_t size) = delete;
private:
int _a;
};
int main() {
StackOnly s = StackOnly::creatObj(2);
StackOnly s2 = s2;
StackOnly s3(s2);
StackOnly* s1 = new StackOnly(2);//error
return 0;
}
4.不能被继承的类
4.1 C++98的实现方式
构造函数私有化,派生类中调用不到基类的构造函数,则无法继承。
cpp
#include <iostream>
using namespace std;
class NonInherit {
public:
// 静态方法:获取类的实例(唯一创建方式)
static NonInherit GetInstance(int a) {
return NonInherit(a);
}
private:
// 构造函数私有化:禁止外部直接创建对象,同时阻止派生类调用
NonInherit(int a)
:_a(a)
{}
private:
int _a;
};
// 尝试继承NonInherit(编译报错)
/*
class Derived :public NonInherit {
public:
Derived():NonInherit(0)// 派生类构造函数需要调用基类构造函数,但基类构造函数私有
{}
};
*/
int main() {
NonInherit n = NonInherit::GetInstance(2);
// 错误用法1:直接创建对象(构造函数私有)
NonInherit n1(3);
// 错误用法2:通过new创建对象(构造函数私有,无法调用)
NonInherit* n2 = new NonInherit(6);
return 0;
}
4.2 C++11的实现方式
final关键字,final修饰类,表示该类不能被继承
cpp
#include <iostream>
using namespace std;
class NonInherit final
{
public:
NonInherit(int a)
:_a(a)
{
}
private:
int _a;
};
// 尝试继承NonInherit(编译报错)
/*
class Derived :public NonInherit {
public:
Derived():NonInherit(0)
{}
};
*/
int main() {
NonInherit n(2);
NonInherit* n2 = new NonInherit(6);
return 0;
}
5.单例模式
5.1 设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。
5.2 单例模式的定义与使用场景
5.2.1 定义
一个类只能创建一个对象,即单例模式。
5.2.2 使用场景
**该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。**比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
5.3 实现模式
5.3.1 饿汉模式
定义: 不管用户将来是否使用,程序启动时(main函数之前) 就创建一个唯一的实例对象。
**优点:**实现容易。
缺点: 这些问题源于其对象创建时机固定在程序启动阶段,缺乏灵活性
- 单例对象初始化内容过多会影响启动速度。
- 当两个单例类存在依赖关系(如 B 依赖 A 且需 A 先创建)时难以处理。
**实现:**用饿汉模式实现了一个存储键值对字典的单例类,确保程序中只有唯一实例且在 main 函数前初始化。
思路:
- 提前创建实例:借助静态成员特性,在程序启动阶段(main 函数执行前)完成唯一实例的初始化。
- 限制创建途径:将构造逻辑设为私有,阻止外部直接创建对象,仅允许通过类提供的接口获取实例。
- 提供全局访问:通过静态方法作为获取实例的唯一入口,确保任何位置都能访问到同一个实例。
- 防止拷贝破坏:禁用拷贝构造和赋值操作,避免通过拷贝或赋值创建新实例,保证唯一性。
- 核心逻辑:利用静态成员初始化时机提前创建实例,结合私有构造与防拷贝机制阻断其他创建方式,实现程序运行期间类的实例全局唯一且可全局访问。
cpp
#include <iostream>
#include <map>
using namespace std;
namespace hungry {
class Singleton {
public:
//提供获取单例对象的接口函数(静态方法,返回唯一实例的引用)
//保证全局只有一个对象,通过该接口访问
static Singleton& getInstance() {
return _sinst;
}
//测试用:自定义成员函数
void fun();
//向内部字典添加键值对
void add(const pair<string, string>& kv) {
_dict[kv.first] = kv.second;
}
//打印字典内部内容
void print() {
for (auto& kv : _dict) {
cout << kv.first << " " << kv.second << endl;
}
}
private:
//构造函数私有化:禁止外部直接创建对象,确保只能通过GetInstance()获取实例
Singleton() {
cout << "Singleton构造函数调用(饿汉模式:main之前初始化)" << endl;
}
//防拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
private:
//单例对象内部数据:存储键值对的字典
map<string, string> _dict;
//静态成员变量:存储唯一的单例对象
static Singleton _sinst;
};
Singleton Singleton::_sinst;
void Singleton::fun() {
_dict["default"] = "饿汉模式单例测试";
}
}
int main() {
cout << "进入main函数" << endl;
// 获取单例对象(两次获取的是同一个实例)
hungry::Singleton& s1 = hungry::Singleton::getInstance();
hungry::Singleton& s2 = hungry::Singleton::getInstance();
// 验证唯一性:打印地址,判断是否相同
cout << "s1对的地址:" << &s1 << " s2的地址:" << &s2 << endl;
// 测试成员函数:添加数据并打印
s1.add({ "hungry","饥饿的" });
s2.add({ "lexical","词汇的" });
s1.fun();
cout << "\n单例内部是数据:" << endl;
s2.print();
// 尝试拷贝(编译报错,验证防拷贝机制)
//hungry::Singleton s3(s1);//error
//hungry::Singleton s4 = s2;//error
return 0;
}

5.3.2 懒汉模式
定义: 核心是延迟初始化,仅在程序首次需要使用实例时才创建唯一的类实例,而非在程序启动阶段提前创建。
优点:
- 资源高效利用:避免初始化未使用的实例,减少程序启动时的资源消耗(如内存、计算资源),尤其适合实例创建成本高的场景。
- 按需加载:符合 "用的时候再创建" 的原则,降低对程序启动速度的影响,适合对启动性能敏感的场景。
缺点:
- 线程安全风险:多线程环境下,若多个线程同时首次请求实例,可能并发创建多个实例,破坏唯一性(需额外同步机制解决,增加实现复杂度)。
- 析构逻辑复杂:实例通常通过动态分配创建,需手动处理销毁(如通过辅助类自动触发或显式调用销毁接口),否则可能导致资源泄漏,相比自动析构的实现更繁琐。
- 首次访问开销:首次获取实例时需执行创建逻辑,可能比提前初始化的方式多一层判断和初始化操作(通常影响较小,但极端场景下需考虑)。
实现:
- 延迟初始化:仅在首次需要时才创建实例,而非程序启动阶段,实现按需加载。
- 控制创建入口:通过私有构造逻辑限制外部直接创建对象,仅允许通过特定接口获取实例。
- 保证唯一性:禁用拷贝和赋值操作,避免通过复制创建新实例,维持全局唯一。
- 自动释放机制:借助静态辅助对象的析构特性,在程序结束时自动触发实例销毁,简化资源管理。
cpp
#include <iostream>
#include <map>
using namespace std;
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& kv : _dict) {
cout << kv.first << " " << kv.second << endl;
}
}
//内部GC类:用于自动释放单例模式
class GC {
public:
~GC()//GC对象析构时,自动调用释放单例
{
lazy::Singleton::delInstance();
}
};
private:
// 构造函数私有:禁止外部直接创建对象
Singleton(){}
// 析构函数私有:仅能通过DelInstance()调用
~Singleton(){}
// 防拷贝:禁用拷贝构造和赋值运算符
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
private:
// 单例内部的数据
map<string, string> _dict;
// 静态指针:指向唯一的单例对象
static Singleton* _psinst;
//静态GC对象:程序结束时自动析构
static GC _gc;
};
Singleton* Singleton::_psinst = nullptr;//初始化静态成员
Singleton::GC Singleton::_gc;//初始化静态GC对象
}
int main() {
// 多次获取单例,验证唯一性(地址相同)
cout << &lazy::Singleton::getInstance() << endl;
cout << &lazy::Singleton::getInstance() << endl;
// 向单例添加数据并打印
lazy::Singleton::getInstance().add({ "lazy", "懒惰的" });
lazy::Singleton::getInstance().add({ "single", "单身的" });
lazy::Singleton::getInstance().print();
return 0;
}
