构造函数
构造函数
当我们自己定义
- 默认构造函数
- 默认析构函数
- 默认拷贝构造函数
- 默认赋值函数
- 移动构造函数
- 移动拷贝函数
如果我们自己定义了构造函数,那么当我们实例化对象时候就会调用相应的构造函数,但是如果我们没有定义自己的构造函数时候,编译器会自动为我们生成一个默认构造函数。
- 隐式声明:用户没有声明,编译器声明。
- 平凡
(trivial) :编译器不会添加代码,编译器什么也不做 - 非平凡(no-trivial)的
// 隐式定义: 编译器会根据需要添加代码,生成函数
一般情况下,用户没有定义这三个函数的话编译器会隐式声明一个对应的函数,编译器隐式声明函数并非一定添加代码,所以又分为平凡的和非平凡的,平凡的对应函数表示编译器不会添加任何代码,非平凡的表示编译器为了满足编译需求会添加一定代码。所以非平凡的对应函数又成为隐式定义的函数。
- 如果用户没有定义这些函数,则编译器会隐式声明一个函数。
- 在编译器需要的情况下(如带有虚函数,虚拟继承等等
) ,会隐式定义函数,这时函数为非平凡的(no-trivial) ;否则则编译器不会添加代码来定义一个函数,这时的函数为平凡的(trivial) 。 - 移动构造隐式声明的条件比较苛刻,不但与用户定义的移动构造有关,还与用户复制构造等有关。
- 平凡的默认构造什么也不做;平凡的拷贝构造和平凡的移动构造操作是相同的,进行按位拷贝。
默认构造函数
默认构造函数是可以无实参调用的构造函数。从定义上看我们知道形如
隐式声明
若不对类类型(struct、
平凡构造函数
平凡构造函数编译器不会做任何事情。
隐式定义/ 非平凡默认构造函数的情况
- 类中有带有默认构造函数的对象成员
类中成员对象若带有默认构造函数,那么为了调用成员对象的默认构造,编译器必须生成部分代码来调用其成员对象的默认构造:
Class S{
public:
int x,y;
S(){ x = 0; y = 0;} // 自己定义的默认构造函数
};
Class F{
public:
S a; // 对象a为类F的成员对象
int i;
};
如上所示,类
// c++ 伪代码
inline F::F(){
a.S::S(); // 调用S 类的默认构造函数
}
那如果用户定义了
Class F{
public:
S a; // 对象a为类F的成员对象
int i;
F(){ i = 0;}
};
编译器会自动为你的
// c++ 伪代码
F::F(){
a.S::S(); // 调用S 类的默认构造函数
i = 0;
}
总之,如果若类成员对象有非平凡(no-trivial)默认构造函数,则编译器会“想尽办法(添加代码)”调用其成员对象的默认构造函数。
- 基类有非平凡默认构造函数
与成员对象含有非平凡构造函数类似,其基类含有非平凡的默认构造函数时,编译器也会生成非平凡默认构造函数,并调用基类的非平凡默认构造函数;当用户定义了
- 带有虚函数
(virtual function) 的类或继承自带有虚函数的类
满足这个条件的类,会有如下两个扩张行动在编译时期发生:
- 一个虚函数表会被编译出来,内存有虚函数地址指针
- 每个类对象中会有一个虚函数指针(vptr)被合成出来,存有虚函数表地址。
所以这就需要对
- 虚拟继承的类(菱形继承)
使用
拷贝构造函数
拷贝构造函数的定义语法如下:
MyClass(const MyClass & t) {...... }
拷贝构造函数的调用场合有三种:
class MyClass{......};
MyClass a;
MyClass b = a; // 对象初始化
// MyClass b;
// void fun(MyClass a);
fun(b); // 将对象b作为参数传入fun
MyClass fun(){
MyClass b;
......
return b;
}
隐式声明
拷贝构造函数可以用用户定义也可以编译器自己生成。若不对类类型(struct、
平凡(trivial)拷贝构造
平凡拷贝构造会进行按位拷贝(Bitwise copy
隐式定义的拷贝构造/ 非平凡拷贝构造
有时候按位拷贝不能满足编译器需要(无法完成语义的需求
- 类成员对象中有非平凡拷贝构造
- 继承一个有非平凡拷贝构造函数的基类
- 类中有虚函数
- 虚拟继承
这四个条件与默认构造函数类似,解释也类似。第
class Base{
public:
int a;
virtual void f(){cout<< "base f";}
};
class Derived: public Base{
public:
int b;
void f() override {cout<< "derived f";}
};
代码所示,类
Derived d;
Base b = d; // 用Derived类d对象初始化Base类对象,会发生切割行为
b.f(); // 输出:base f
这种操作是合法的,但是会发生切割行为,但是这个时候如果发生按位拷贝,那么对象
移动构造函数
移动构造函数与拷贝构造类似,不同的是初始化对象带有移动语义。移动构造函数语法如下:
MyClass(const MyClass && t) {...... }
移动构造函数调用场景
当(以直接初始化或复制初始化)从同类型的右值(亡值或纯右值)
- 以一个对象初始化另一个对象如:
class MyClass{......};
MyClass a;
MyClass b = std::move(a); // 对象初始化
- 函数参数的对象传递
// MyClass b;
// void fun(MyClass a);
fun(std::move(b)); // 将对象b作为参数传入fun
- 作为参数的返回值,有移动构造函数
MyClass fun(){
MyClass b;
......
return b;
}
隐式声明的移动构造函数(编译器会自动生成的)
若不对类类型(struct、
- 没有用户声明的复制构造函数;
- 没有用户声明的复制赋值运算符;
- 没有用户声明的移动赋值运算符;
- 没有用户声明的析构函数;
隐式声明的移动构造函数并未因为下一节所详述的条件而被定义为弃置的
弃置的隐式声明的移动构造函数(不会产生移动构造函数)
若下列任何一项为真,则类
T 拥有无法移动(拥有被弃置、不可访问或有歧义的移动构造函数)的非静态数据成员;T 拥有无法移动(拥有被弃置、不可访问或有歧义的移动构造函数)的直接或虚基类;T 拥有带被弃置或不可访问的析构函数的直接或虚基类;T 是联合式的类,且拥有带非平凡移动构造函数的变体成员;T 拥有非静态数据成员或直接或虚基类,它无法平凡复制且没有移动构造函数。(C++14 前)
重载决议忽略被弃置的隐式声明的移动构造函数(否则它会阻止从右值复制初始化
平凡移动构造函数
当下列各项全部为真时,类
- 它不是用户提供的(即它是隐式定义或预置的
) ; T 没有虚成员函数;T 没有虚基类;- 为
T 的每个直接基类选择的移动构造函数都是平凡的; - 为
T 的每个类类型(或类类型数组)的非静态成员选择的移动构造函数都是平凡的;
隐式定义的移动构造函数(非平凡移动构造函数)
若隐式声明的移动构造函数既未弃置亦非平凡,则当其被