目录
1、设计一个不能被拷贝的类
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
在C++98中,将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
原因:
1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就不能禁止拷贝了
2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
在C++11中,拓展了delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
}
2、设计一个只能在堆上创建空间的类
2.1 方法一
正常的类是可以在栈上、堆上、静态区开空间的
class HeapOnly
{
};
int main()
{
HeapOnly hp1;
HeapOnly* hp2 = new HeapOnly;
static HeapOnly hp3;
return 0;
}
这三种创建对象的方式都需要用到构造函数,所以我们可以将构造函数写为私有,然后提供一个统一创建对象的函数CreateObj,CreateObj里面实现为在堆上创建空间。注意,因为构造函数被写成了私有,所以没办法先创建一个类对象,然后利用这个对象去调用CreateObj,所以CreateObj需要写成static
class HeapOnly
{
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
private:
HeapOnly()
{}
};
int main()
{
HeapOnly::CreateObj();
return 0;
}
此时是有缺陷的,因为有拷贝构造和赋值运算符重载可以在栈上开空间
class HeapOnly
{
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
private:
HeapOnly()
{}
};
int main()
{
HeapOnly* hp1 = HeapOnly::CreateObj();
HeapOnly hp2(*hp1);
HeapOnly hp3 = *hp1;
return 0;
}
所以我们需要将拷贝构造和赋值运算符重载也封了
class HeapOnly
{
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
HeapOnly(const HeapOnly& hp) = delete;
HeapOnly& operator=(const HeapOnly& hp) = delete;
private:
HeapOnly()
{}
};
注意:这里不能将构造函数封了,只能设置为私有,因为CreateObj里面的new需要调用构造函数
要释放时直接delete
int main()
{
HeapOnly* hp1 = HeapOnly::CreateObj();
delete hp1;
return 0;
}
2.2 方法二
方法一是将构造函数私有化,还可以将析构函数私有化,并提供Destory函数
class HeapOnly
{
public:
void Destory()
{
delete this;
}
private:
~HeapOnly()
{}
};
int main()
{
HeapOnly hp1;
HeapOnly* hp2 = new HeapOnly;
static HeapOnly hp3;
return 0;
}
此时只有hp2是可以创建成功的,hp1和hp3创建失败是因为无法调用析构函数,hp2之所以能够成功,是因为new的对象是只管开辟空间,释放需要自己调用delete,此时delete hp2是会报错的,因为delete会去调用析构函数,而析构函数是私有的,这也是为什么要写一个Destory函数的原因
此时就没有必要将拷贝构造等封死了,因为没有析构函数,无法创建对象
3、设计一个只能在栈上创建空间的类
将构造函数封死,提供一个CreateObj,这个CreateObj内部实现为在栈上开空间
class StackOnly
{
public:
static StackOnly CreateObj()
{
return StackOnly();
}
private:
StackOnly()
:_a(0)
{}
private:
int _a;
};
int main()
{
StackOnly s1 = StackOnly::CreateObj();
return 0;
}
这样是有问题的,依然可以利用拷贝构造在堆上开空间
int main()
{
StackOnly s1 = StackOnly::CreateObj();
StackOnly* s2 = new StackOnly(s1);
return 0;
}
这里不能将拷贝构造封死,因为CreateObj是传值返回的(局部对象只能走传值返回),会调用拷贝构造,所以不能封死CreateObj。new是由operator new和构造组成的,当然,在s2这里是operator和拷贝构造组成的,operator是一个全局函数,C++规定,当一个类重载了operator new 后,则只会调用自己的,所以此时可以重载一个专属的operator new,并将这个函数delete,可以顺便将operator delete也delete
class StackOnly
{
public:
static StackOnly CreateObj()
{
return StackOnly();
}
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
private:
StackOnly()
:_a(0)
{}
private:
int _a;
};
此时依然有问题,可以在静态区开空间,因为依然有拷贝构造
int main()
{
StackOnly s1 = StackOnly::CreateObj();
static StackOnly s3(s1);
return 0;
}
此时可以直接封死拷贝构造,写一个移动构造
class StackOnly
{
public:
static StackOnly CreateObj()
{
return StackOnly();
}
StackOnly(StackOnly&& s)
{}
StackOnly(const StackOnly& s) = delete;
private:
StackOnly()
:_a(0)
{}
private:
int _a;
};
此时CreateObj中的值就是右值,刚好调用移动构造,即使CreateObj中的是左值,也可以move,这样在返回过程中就只会调用移动构造了
此时还是有问题,像下面的情况都行
int main()
{
StackOnly s1 = StackOnly::CreateObj();
StackOnly* s2 = new StackOnly(move(s1));
static StackOnly s3(move(s1));
return 0;
}
所以还是上面封死operator new的方法好
4、设计一个不能被继承的类
C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
C++11中,新增了final关键字,final修饰类,表示该类不能被继承
class A final
{
// ....
};
5、设计一个只能创建一个对象的类(单例模式)
5.1 设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
我们前面学习过的迭代器模式、适配器模式也都是设计模式
单例模式
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
注意:这个对象创建在哪里都行,只要只有一个对象就行
实现单例模式主要有两种方式:饿汉模式和懒汉模式
5.2 饿汉模式
就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象
在类中创建一个静态的类对象,这个类对象不属于类,是一个全局对象,放在类中只是为了让其受到类域的限制,这个类的成员变量只有前面那些。放在类里面只是声明,还需要在类外面定义以下。注意,还需要将构造函数封死。这样在类外部就无法创建类对象,整个程序就只有类里面的那个全局对象,这个全局对象是还没有进入main时类对象就已经创建了。
class InfoMgr
{
public:
static InfoMgr& GetInstance()
{
return _ins;
}
void Print()
{
cout << _ip << " " << _port << " " << _buffSize << endl;
}
private:
InfoMgr()
{}
private:
string _ip = "127.0.0.1";
int _port = 80;
size_t _buffSize = 1024 * 1024;
// ...
static InfoMgr _ins;
};
InfoMgr InfoMgr::_ins;
int main()
{
InfoMgr::GetInstance().Print();
InfoMgr im = InfoMgr::GetInstance();
im.Print();
return 0;
}
此时仍然可以使用拷贝构造来再创建一个对象,所以封死拷贝构造
int main()
{
InfoMgr copy(InfoMgr::GetInstance());
return 0;
}
class InfoMgr
{
public:
static InfoMgr& GetInstance()
{
return _ins;
}
void Print()
{
cout << _ip << " " << _port << " " << _buffSize << endl;
}
InfoMgr(const InfoMgr& io) = delete;
InfoMgr& operator=(const InfoMgr& io) = delete;
private:
InfoMgr()
{}
private:
string _ip = "127.0.0.1";
int _port = 80;
size_t _buffSize = 1024 * 1024;
// ...
static InfoMgr _ins;
};
InfoMgr InfoMgr::_ins;
饿汉模式有两个缺陷。第一,饿汉模式中这个唯一的类对象是在运行main函数之前就创建好的,也就是在main函数之前就会调用构造函数,若调用构造函数的消耗较大,如需要读取大文件,且有多个单例对象时,程序启动会非常慢。第二,若有A和B两个饿汉,且对象初始化存在依赖关系,要求A先初始化,B后初始化,饿汉无法完成。
5.3 懒汉模式
懒汉模式就是在要用时再创建对象(第一次调用时创建对象)
也需要将构造函数设为私有,封死拷贝构造和赋值运算符重载。
此时是用一个类对象的指针。饿汉模式不存在释放问题,因为是全局对象,懒汉是new的,需要手动释放
class InfoMgr
{
public:
static InfoMgr& GetInstance()
{
// 第一次调用时创建单例对象
if (_pins == nullptr)
_pins = new InfoMgr;
return *_pins;
}
static void DelInstance()
{
delete _pins;
_pins = nullptr;
}
void Print()
{
cout << _ip << " " << _port << " " << _buffSize << endl;
}
InfoMgr(const InfoMgr& io) = delete;
InfoMgr& operator=(const InfoMgr& io) = delete;
private:
InfoMgr()
{
cout << "InfoMgr()" << endl;
}
private:
string _ip = "127.0.0.1";
int _port = 80;
size_t _buffSize = 1024 * 1024;
// ...
static InfoMgr* _pins;
};
InfoMgr* InfoMgr::_pins = nullptr;
int main()
{
InfoMgr::GetInstance().Print();
InfoMgr::GetInstance().Print();
return 0;
}
结果是