一、C++类默认定义的函数
- 如果class没有定义构造函数、析构函数、拷贝构造函数、拷贝赋值运算符函数,那么C++内部会动创建一个默认的构造函数、析构函数、拷贝构造函数、拷贝赋值运算符函数。并且这些默认函数都是public且inline的
演示说明
class Empty{}; //空类
Empty e1; //调用e1的默认构造函数
Empty e2(e1);//调用e2的默认拷贝构造函数
e2 = e1; //调用e2的默认拷贝赋值运算符
//程序结束
//调用e1与e2的默认析构函数
二、默认构造函数、默认析构函数做了什么?
- 默认构造函数与默认析构函数主要是给编译器一个地方用来防止“隐藏幕后”的代码,像是调用基类和非静态成员变量的构造函数和析构函数
- 注意:编译器定义的默认析构函数是非virtual的,除非这个class的基类自身有virtual析构函数(这种情况下这个函数的虚属性(virtual)来自于基类)
三、默认拷贝构造函数、默认拷贝赋值运算符做了什么?
- 默认拷贝构造函数/默认拷贝赋值运算符一样,都是在拷贝/对象赋值时将类的每一个非静态成员变量逐个拷贝到目标对象之中
拷贝构造示案例
template<typename T>
class NamedObject
{
public:
//两个构造函数
NamedObject(const char* name, const T& value);
NamedObject(const std::string& name, const T& value);
private:
std::string name;
T objectValue;
};
NamedObject<int> no1("dongshao", 2);
NamedObject<int> no2(no1); //调用NamedObject<int>的默认拷贝构造函数
- 上面的no2对象调用默认的拷贝构造函数来拷贝no1对象,其中的操作有:
- 将no1对象的name数据成员拷贝给no2对象的name数据成员(又因为name为string类型,所以最终会调用string的拷贝构造函数来拷贝name)
- 因为此程序中模板类型为int,所以objectValue为int内置类型,直接将no1对象的objectValue赋值给no2的objectValue
- 对于赋值运算符来说,其与拷贝构造函数一样,也是逐个将数据成员进行重新赋值
拷贝赋值运算符的错误注意事项
- 虽然拷贝构造函数与拷贝赋值运算符用起来比较类似,但是有一种情况会导致出错,见下面的演示案例
template<typename T>
class NamedObject
{
public:
NamedObject(std::string& name, const T& value);
private:
//此处将name改为了引用形式,objectValue改为了const类型
std::string& name;
const T objectValue;
};
std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(oldDog,36);
p = s; //错误
- 上述“p=s”错误的原因:
- name:因为name为引用类型了,我们知道引用一旦绑定就不可以改变引用指向,但是在此处拷贝赋值运算符中,我们将p的name引用从一开始指向于newDog又指向了oldDog,因此会发生错误
- objectValue:因为objectValue为const类型,因此在初始化之后其值就不可以改变了,此处将p的值从2改变为36因此会发生错误
四、拒绝生成默认函数
- 从“三”中的演示案例可以看到,在某些情况下,使用了系统提供的默认拷贝赋值运算符会出现错误。因此有些情况下我们不希望使用class提供的默认拷贝赋值运算符,那么就需要提供一种方法
- 如果一个基类将拷贝赋值运算符声明为private,那么编译器就拒绝为派生类生成默认的拷贝赋值运算符。因为基类如果想使用这些函数,那么这些函数会相应的处理基类成份,但是基类是private的,所以不可以使用
- 这种方法不仅适用于拷贝赋值运算符,对于构造函数、拷贝构造函数都同样有效
五、总结
编译器可以暗自为class创建默认构造函数、拷贝构造函数、拷贝赋值函数以及析构函数。