设计思路:
拷贝只会发生在两个场景中:拷贝构造和赋值重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造以及赋值重载即可。
C++98方案:
将拷贝构造与赋值重载只声明不定义,并且将其访问权限设置为私有即可。
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
原因:
设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就不能禁止拷贝了。
只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11方案:
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete
,表示让编译器删除掉该默认成员函数。
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
思路一:将构造、拷贝构造函数私有
class HeapOnly
{
int _val;
// 把构造和拷贝构造设置成私有
HeapOnly(int val = 0)
: _val(val)
{
}
// 一定要把拷贝构造也设为私有
HeapOnly(const HeapOnly &obj);
public:
// 提供一个静态的成员函数,使用new申请堆空间并调用构造函数完成堆对象的创建。
static HeapOnly *CreateObj(int val = 0)
{
return new HeapOnly(val);
}
};
int main()
{
// HeapOnly obj;
HeapOnly *pobj1 = HeapOnly::CreateObj(10);
// HeapOnly obj(*pobj1);
return 0;
}
思路二:将析构函数私有
编译器在为类对象分配栈空间时,会先检查类的构造和析构函数的访问性。由于栈的创建和释放都需要由系统完成的,所以若是无法调用构造或者析构函数,自然会报错。如果类的析构函数是私有的,则编译器将报错。
当然为了我们能够释放动态创建的对象,我们必须提供一个公有函数,该函数的唯一功能就是删除堆对象。
delete this
调用析构函数清理对象资源并释放堆空间。class HeapOnly
{
int _val;
// 把析构设置成私有
~HeapOnly()
{
cout << "~HeapOnly()" << endl;
}
public:
HeapOnly(int val = 0)
: _val(val)
{
}
// 提供一个公有的成员函数,执行delete this调用析构函数清理对象资源并释放堆空间
void DestroyObj()
{
delete this;
}
};
int main()
{
// HeapOnly obj;
HeapOnly *pobj = new HeapOnly(10);
// HeapOnly obj(*pobj);
// delete pobj;
pobj->DestroyObj();
return 0;
}
思路:重载operator new
我们还可以将new操作符重载并设置为私有访问。
class StackOnly
{
int _val;
void* operator new(size_t t);
public:
StackOnly(int val = 0)
: _val(val)
{
}
StackOnly(const StackOnly &obj)
: _val(obj._val)
{
}
};
int main()
{
StackOnly obj(10);
StackOnly obj1(obj);
// StackOnly *pobj = new StackOnly(10);
// StackOnly *pobj1 = new StackOnly(obj);
return 0;
}
C++98方案:将构造函数私有
派生类中调不到基类的构造函数,则无法继承。
class NonInherit
{
public:
static NonInherit CreatObj()
{
return NonInherit();
}
private:
NonInherit()
{}
};
C++11方案:final关键字
final修饰类,表示该类不能被继承。
class A final
{
// ....
};
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的代码设计经验总结。
使用设计模式的目的:
为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
常用的设计模式:
单例模式:
所谓饿汉模式,就是说不管你将来用不用,程序启动时(main函数之前)就创建一个唯一的实例对象。
设计思路:
class Singleton
{
// 成员变量
vector<string> _dir;
// 该类的静态指针,提供一个访问单例的全局访问点
static Singleton *s_ins;
// 互斥锁成员,保证多线程互斥访问该单例
mutex s_mtx;
// 静态的内部类对象,该对象析构时会顺便析构单例,自动释放
struct GC
{
~GC()
{
if (s_ins != nullptr)
{
delete s_ins;
s_ins = nullptr;
}
}
};
static GC s_gc;
// 私有构造、拷贝构造和析构,保证系统中该类只有一个实例
Singleton()
{
cout << "Singleton()" << endl;
};
Singleton(const Singleton &st);
~Singleton()
{
// 单例对象的析构一般会做一些持久化操作(数据落盘)
// ......
cout << "~Singleton()" << endl;
}
public:
// 提供一个静态成员函数,用于获取全局访问点(静态指针)
static Singleton *GetInstance()
{
return s_ins;
}
void Add(const string &name)
{
s_mtx.lock();
_dir.push_back(name);
s_mtx.unlock();
}
void Print()
{
s_mtx.lock();
for (auto &name : _dir)
{
cout << name << endl;
}
s_mtx.unlock();
}
};
// 程序启动时(main函数之前)创建
Singleton *Singleton::s_ins = new Singleton;
Singleton::GC Singleton::s_gc;
int main()
{
// 系统中该类只有一个实例,不允许通过任何方式实例化
// Singleton st;
// static Singleton st1;
// Singleton* pst = new Singleton;
// Singleton st(*(Singleton::GetInstance()));
// 单线程场景
// Singleton::GetInstance()->Add("张三");
// Singleton::GetInstance()->Add("李四");
// Singleton::GetInstance()->Add("王五");
// Singleton::GetInstance()->Print();
// 多线程场景
int n = 6;
srand((unsigned int)time(nullptr));
thread t1([n]() mutable
{
while(n--)
{
Singleton::GetInstance()->Add("线程1:" + to_string(rand()));
this_thread::sleep_for(chrono::milliseconds(10));
} });
thread t2([n]() mutable
{
while(n--)
{
Singleton::GetInstance()->Add("线程2:" + to_string(rand()));
this_thread::sleep_for(chrono::milliseconds(10));
} });
t1.join();
t2.join();
Singleton::GetInstance()->Print();
}
运行结果(多线程场景):
设计思路:
// 饿汉模式2
class Singleton
{
// 成员变量
vector<string> _dir;
// 该类的静态对象,提供一个访问单例的全局访问点
static Singleton s_ins;
// 互斥锁成员,保证多线程互斥访问该单例
mutex s_mtx;
// 私有构造、拷贝构造和析构,保证系统中该类只有一个实例
Singleton()
{
cout << "Singleton()" << endl;
};
Singleton(const Singleton &st);
// 由于单例是在静态区创建的,进程结束时,系统会自动调用单例析构释放其资源。
~Singleton()
{
// 单例对象的析构一般会做一些持久化操作(数据落盘)
// ......
cout << "~Singleton()" << endl;
}
public:
// 提供一个静态成员函数,用于获取全局访问点(静态对象的引用)
static Singleton &GetInstance()
{
return s_ins;
}
void Add(const string &name)
{
s_mtx.lock();
_dir.push_back(name);
s_mtx.unlock();
}
void Print()
{
s_mtx.lock();
for (auto &name : _dir)
{
cout << name << endl;
}
s_mtx.unlock();
}
};
// 程序启动时(main函数之前)创建
Singleton Singleton::s_ins;
运行结果:同上
饿汉模式的缺点:
提示:饿汉模式的全局访问点除了定义静态指针还可以直接定义成静态对象。如果是静态对象,进程在退出时会自动调用其析构函数。
如果单例对象的构造十分耗时或者占用很多资源,比如加载插件、 初始化网络连接、读取文件等等。而且有可能程序运行时不会用到该对象,如果也在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
所谓懒汉模式,就是在任意程序模块第一次访问单例时实例化对象。
设计思路:
// 懒汉模式
class Singleton
{
// 成员变量
vector<string> _dir;
// 该类的静态指针,提供一个访问单例的全局访问点
static Singleton *s_ins;
// 静态互斥锁,保证多线程互斥地创建和访问该单例
static mutex s_mtx;
// 静态的内部类对象,该对象析构时会顺便析构单例,自动释放
struct GC
{
~GC()
{
if (s_ins != nullptr)
{
delete s_ins;
s_ins = nullptr;
}
}
};
static GC gc;
// 私有构造、拷贝构造和析构,保证系统中该类只有一个实例
Singleton()
{
cout << "Singleton()" << endl;
};
Singleton(const Singleton &st);
~Singleton()
{
// 单例对象的析构一般会做一些持久化操作(数据落盘)
// ......
cout << "~Singleton()" << endl;
}
public:
static Singleton *GetInstance()
{
// 懒汉模式:在第一次访问实例时创建
// 双检查加锁
if (s_ins == nullptr) // 第一道检查:提高效率,不需要每次获取单例都加锁解锁
{
s_mtx.lock();
if (s_ins == nullptr) // 第二道检查:保证线程安全和只new一次
{
s_ins = new Singleton;
}
s_mtx.unlock();
}
return s_ins;
}
void Add(const string &name)
{
s_mtx.lock();
_dir.push_back(name);
s_mtx.unlock();
}
void Print()
{
s_mtx.lock();
for (auto &name : _dir)
{
cout << name << endl;
}
s_mtx.unlock();
}
// 一般单例对象的生命周期随进程,系统会在进程退出时释放其内存,不需要中途析构单例对象
// 不过在一些特殊场景下,可能需要进行显示手动释放
static void DelInstance()
{
s_mtx.lock();
if (s_ins != nullptr)
{
delete s_ins;
s_ins = nullptr;
}
s_mtx.unlock();
}
};
// 静态成员要在类外定义
Singleton *Singleton::s_ins = nullptr;
mutex Singleton::s_mtx;
Singleton::GC Singleton::gc;
运行结果(多线程场景):
设计思路:
// 懒汉模式2
class Singleton
{
// 成员变量
vector<string> _dir;
// 互斥锁成员,保证多线程互斥访问该单例
mutex s_mtx;
// 私有构造、拷贝构造和析构,保证系统中该类只有一个实例
Singleton()
{
cout << "Singleton()" << endl;
};
Singleton(const Singleton &st);
~Singleton()
{
// 单例对象的析构一般会做一些持久化操作(数据落盘)
// ......
cout << "~Singleton()" << endl;
}
public:
static Singleton *GetInstance()
{
// C++11之前,这里不能保证初始化静态对象的线程安全问题
// C++11之后,这里可以保证初始化静态对象的线程安全问题
static Singleton s_ins; //首次调用时创建局部静态对象
return &s_ins;
}
void Add(const string &name)
{
s_mtx.lock();
_dir.push_back(name);
s_mtx.unlock();
}
void Print()
{
s_mtx.lock();
for (auto &name : _dir)
{
cout << name << endl;
}
s_mtx.unlock();
}
};
运行结果:同上
懒汉模式模式完美解决了饿汉模式的问题,就是相对复杂一些。