一·编译器自动生成了哪些成员函数?
成员函数 | 作用 |
---|---|
构造函数 | 初始化对象成员(不是开辟空间,开辟空间是new) |
析构函数 | 编译器销毁对象,然后自动调用析构函数清理资源 |
拷贝构造函数 | 已存在的对象初始化新对对象时调用 |
运算符重载 | 已存在对象赋值已存在对象时调用 |
取地址重载 | 不常重载 |
const取地址重载 | 不常重载 |
关于编译器自动生成的函数的更加详细的介绍,请参考:
「Effective C++」条款05: 了解c++默默编写并调用了哪些函数
二·为什么会不想使用编译器自动生成的函数?
经典的例子就是设计模式的单例模式【Singleton】,在单例模式中,我们要求对象只有一份,所以必须拒绝被一些编译器生成的函数来防止对象被拷贝(对象被拷贝使用的是拷贝构造函数的赋值运算符重载,所以应当拒绝这两个函数,但是在实际的工作中需要拒绝哪些函数应该根据实际情况来判断)。
·简单单例模式代码举例:
class Singleton
{
public:
Singleton(const Singleton& obj) = delete;
Singleton& operator=(const Singleton& obj) = delete;
static Singleton* getInstance()
{
return m_obj;
}
private:
Singleton() = default;
~Singleton() = default;
static Singleton* m_obj;
};
Singleton* Singleton::m_obj=new Singleton;
int main()
{
Singleton* obj=Singleton::getinstance();
}
在这个例子中,我们要求m_obj
始终只有一份拷贝,所以需要拒绝拷贝构造函数和运算符重载,防止对象被拷贝。
三·编译器生成的成员函数的特性
- 编译器默认生成的成员函数的属性为:
public
- 用户写了相应的成员函数,编译器则不再自动生成
- 编译器默认生成的函数都可以调用
四·几种拒绝的方式和优缺点
·方式一:将拷贝构造函数和赋值运算符重载显式声明为private
,函数定义为空
优点:操作简单,能够应付大多数场景
缺点:友元依旧可以操作定义为private
的成员函数,或者使用公共成员函数也可以调用声明为private
的成员函数
代码举例:
class Student
{
public:
Student(){}
~Student(){}
private:
Student(const Singleton& obj){} //声明为private,并定义为空来防止拒绝使用这两个函数
Student& operator=(const Singleton& obj){}
}
·方式二:使用= delete
优点:操作更简单,更加直观的表示要禁用这个函数
示例代码:
class Student
{
public:
Student(){}
~Student(){}
private:
Student(const Singleton& obj) = delete; //直接禁用
Student& operator=(const Singleton& obj) = delete;
}
·方式三:使用继承的方式
优点:比较精妙(…)
缺点:需要继承,操作相比其他方式稍显复杂
//首先设计基类
class People
{
public:
People(){}
~People(){}
private:
People(const Singleton& obj); //一定要设置为private
People& operator=(const Singleton& obj);
}
//设计子类,继承于People类
class Student : public People
{
public:
Student(){}
~Student(){}
}
int main()
{
Student Tom;
Student Jack;
Jack = Tom; //编译器报错,原因:我们都只道,子类使用到构造函数的时候会优先调用父类的构造函数,而拷贝构造函数也是同样的会被先于子类的
//构造函数调用,但是父类的拷贝构造函数被声明为private,所以调用失败,所以子类的对象赋值失败,达到拒绝的目的。
}