C++特殊类的设计
1.设计一个类,这个类不能拷贝
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
- 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;
//...
};
2. 设计一个类,这个类只能在堆上创建对象
实现方式:
- 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
- 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
class HeapOnly
{
public:
template <class... Args>
static HeapOnly* CreateObject(Args&&...args)
{
return new HeapOnly(args...);
}
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
private:
HeapOnly(int a, int b) { x = a; y = b; }
int x;
int y;
};
方法二:禁用析构函数
class HeapOnly
{
public:
template <class... Args>
static HeapOnly* CreateObject(Args&&...args)
{
return new HeapOnly(args...);
}
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
HeapOnly(int a, int b) { x = a; y = b; }
HeapOnly() {};
void Destroy()
{
delete this;
}
private:
~HeapOnly()
{
cout << "~HeapOnly() " << endl;
};
int x;
int y;
};
int main()
{
HeapOnly* p = HeapOnly::CreateObject(1, 1);
p->Destroy();
//因为这里shared_ptr默认定制删除器delete,但是这里delete调用不了,所以要自己定制一个
shared_ptr<HeapOnly> p2(new HeapOnly, [](HeapOnly* ptr) {ptr->Destroy(); });
return 0;
}
3. 设计一个类,这个类只能在栈上创建对象
方法同上将构造函数私有化,然后设计静态方法创建对象返回即可。
class StackOnly
{
public:
template <class... Args>
static StackOnly CreateObject(Args&&...args)
{
return StackOnly();
}
//StackOnly(const StackOnly&) = delete;//这里就不能把拷贝构造给禁用,因为CreateObject是值返回,需要拷贝构造
StackOnly& operator=(const StackOnly&) = delete;
//解决这种StackOnly* p2 = new StackOnly(p);
//因为如果不重载new的话,就可以使用new创建出一个在堆上的类,而new使用的是全局的new,但是这里如果我们自己重载一个的话它就会使用我们重载出来的了。
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
private:
StackOnly() { }
};
int main()
{
StackOnly p = StackOnly::CreateObject();
return 0;
}
4. 设计一个类,这个类不能被继承
- C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
因为继承对象需要调用被继承对象的构造 - C++11方法final关键字,final修饰类,表示该类不能被继承。
class A final
{
// ....
};
5. 设计一个类,这个类只能被创建一次 (单例模式)
设计模式:
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:
- 饿汉模式
就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
class Singleton
{
public:
//因为只有static的函数才能调用static的成员
static Singleton* CtreaSing()
{
return &_sign;
}
void Print()
{
cout << _x << endl;
cout << _y << endl;
for (auto& x : _vstr)
{
cout << x << " ";
}
cout << endl;
}
void Add(const string& str)
{
_vstr.push_back(str);
}
//同时讲拷贝构造和赋值构造禁止
Singleton(const Singleton& s) = delete;
Singleton& operator= (const Singleton & s) = delete;
private:
Singleton(int x = 0, int y = 0, const vector<string>& vstr = {"xxx", "yyy"})
:_x(x),
_y(y),
_vstr(vstr)
{}
int _x;
int _y;
vector<string> _vstr;
//声明一个静态的类对象
//注意这里不存在递归式的声明和创建,因为这里是被static修饰的成员,被static修饰的成员是不存在类中的而是存放在静态区的
static Singleton _sign;
};
//类内声明,类外定义
//此时当运行是这个对象就被常见了,而且只有一份
Singleton Singleton::_sign(1, 1, {"hehe", "haha"});
int main()
{
Singleton::CtreaSing()->Print();
Singleton::CtreaSing()->Add("2222");
Singleton::CtreaSing()->Print();
return 0;
}
饿汉的缺点
- 因为饿汉模式是一来就创建对象,也就是说一来就是初始化,这样做的话如果这个类非常的大构造成本开销就很大,那么如果某个 程序使用了这个方法的话,那么这个程序的启动也会受到影响。
- 如果多个单例存在依赖关系的话,会无法控制。比如存在A和B两个单例类,现在要求先构造A后构造B,而这是无法控制的,因为他们两个都是单例类,都是全局静态的,谁先构造无法得知。
- 懒汉模式
class Singleton
{
public:
//因为只有static的函数才能调用static的成员
static Singleton* CtreaSing()
{
//调用的时候初始化
if (_psign == nullptr)
{
_psign = new Singleton;
}
return _psign;
}
static void DeleteSing()
{
if (_psign)
{
delete _psign;
_psign = nullptr;
}
}
void Print()
{
cout << _x << endl;
cout << _y << endl;
for (auto& x : _vstr)
{
cout << x << " ";
}
cout << endl;
}
void Add(const string& str)
{
_vstr.push_back(str);
}
//同时讲拷贝构造和赋值构造禁止
Singleton(const Singleton& s) = delete;
Singleton& operator= (const Singleton& s) = delete;
private:
Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "xxx", "yyy" })
:_x(x),
_y(y),
_vstr(vstr)
{
cout << "lazy Singleton" << endl;
}
int _x;
int _y;
vector<string> _vstr;
//我可以声明一个指针,这样子当创建的时候我们在初始化
static Singleton* _psign;
~Singleton()
{
cout << "~Singleton()" << endl;
}
//为了防止忘记delete可以用一个类包起来,这样这个类出了作用域就会调用析构
//内部类
class GC
{
public:
~GC()
{
Singleton::DeleteSing();
}
};
static GC gc;
};
//类内声明,类外定义
//此时当运行是这个对象就被常见了,而且只有一份
Singleton* Singleton::_psign = nullptr;
Singleton::GC Singleton::gc;
但是C++11后懒汉就可以得到一个优化:
class Singleton
{
public:
//因为只有static的函数才能调用static的成员
static Singleton* CtreaSing()
{
//局部的静态变量,会在第一次调用函数的时候构造初始化
//C++11后才可以这样写
//C++11之前无法保证线程安全问题——线程我们后序话进行讲解
static Singleton sign;
return &sign;
}
void Print()
{
cout << _x << endl;
cout << _y << endl;
for (auto& x : _vstr)
{
cout << x << " ";
}
cout << endl;
}
void Add(const string& str)
{
_vstr.push_back(str);
}
//同时讲拷贝构造和赋值构造禁止
Singleton(const Singleton& s) = delete;
Singleton& operator= (const Singleton& s) = delete;
private:
Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "xxx", "yyy" })
:_x(x),
_y(y),
_vstr(vstr)
{
cout << "lazy Singleton" << endl;
}
int _x;
int _y;
vector<string> _vstr;
//我可以声明一个指针,这样子当创建的时候我们在初始化
static Singleton* _psign;
~Singleton()
{
if (_psign)
{
delete _psign;
_psign = nullptr;
}
cout << "~Singleton()" << endl;
}
};
//类内声明,类外定义
//此时当运行是这个对象就被常见了,而且只有一份
Singleton* Singleton::_psign = nullptr;