C++学习笔记,初始化列表与构造函数
一,初始化列表
在开始执行组成构造函数体的复合语句之前,所有的直接基类,虚基类,及非静态数据成员的初始化均已结束。成员初始化列表是能指定这些对象的非默认初始化之处,对于不能默认初始化的基类或非静态数据成员,例如引用和const限定的类型的成员,必须指定成员初始化器,对没有成员初始化器的匿名联合体或变体成员不进行初始化
二.委托构造函数
若类自身的名字在初始化器列表中作为 类或标识符 出现,则该列表必须仅由这一个成员初始化器组成;这种构造函数被称为委托构造函数(delegating constructor),而构造函数列表的仅有成员所选择的构造函数是目标构造函数
此情况下,首先由重载决议选择目标构造函数并予以执行,然后控制返回到委托构造函数并执行其函数体。委托构造函数不能递归。
1 class Foo { 2 public: 3 Foo(char x, int y) {} 4 Foo(int y) : Foo('a', y) {} // Foo(int) 委托到 Foo(char,int) 5 };
初始化顺序:列表中的成员初始化器的顺序是不相关的:
1)若构造函数时最终派生类,则按基类声明的深度优先、从左到右的遍历中的出现的顺序(从左到右指的是基说明符列表中程现的),初始化各个虚基类
2)然后,以在此类的基类说明符列表中出现的从左到右顺序,初始化各个直接基类
3)然后,以类定义中的声明顺序,初始化非静态成员
4)最后,执行构造函数体。
三.转换构造函数
不以说明符explicit 声明且可以单个参数调用 (C++11 前)的构造函数被称为转换构造函数(converting constructor)。
与只在直接初始化(包括static_cast显式转换)中被考虑的explicit构造函数不同,转换构造函数还作为用户定义的转换序列中的一部分,在复制初始化中受到考虑
通常说法是转换构造函数指定了一个从其实参类型(若存在)到其类类型的隐式转换。注意非 explicit 用户定义转换函数也指定一个隐式转换。
隐式声明的及用户定义的非 explicit 复制构造函数与移动构造函数也是转换构造函数。
四,复制构造函数
类名(类名 &)
凡在对象从同类型的另一对象(以直接初始化或复制初始化)初始化时,调用复制构造函数。
1)初始化:T a=b或T a(b);b为类型T
2)函数实参传递f(a),其中a类型为T而f(T t);
3函数返回:在如T f()这样函数内部return a;a类型为T,它没有移动构造函数
隐式声明的复制构造函数
若不对类提供任何用户定义的复制构造函数,编译器始终会声明一个复制构造函数,作为其类非explicit的inline public 成员。
当以下各项均为真时,这个隐式声明的复制构造函数拥有形式 T::T(const T&):
T
的每个直接与虚基类B
均拥有复制构造函数,其形参为 const B& 或 const volatile B&;T
的每个类类型或类类型数组的非静态数据成员M
均拥有复制构造函数,其形参为 const M& 或 const volatile M&。
否则,隐式声明的复制构造函数是 T::T(T&)。(注意因为这些规则,隐式声明的复制构造函数不能绑定到 volatile 左值实参)。
类可以拥有多个复制构造函数,如 T::T(const T&) 和 T::T(T&)。
当存在用户定义的复制构造函数时,用户仍可用关键词 default
强迫编译器生成隐式声明的复制构造函数。(C++11)
1 struct A 2 { 3 int n; 4 A(int n = 1) : n(n) { } 5 A(const A& a) : n(a.n) { } // 用户定义的复制构造函数 6 }; 7 8 struct B : A 9 { 10 // 隐式默认构造函数 B::B() 11 // 隐式复制构造函数 B::B(const B&) 12 }; 13 14 struct C : B 15 { 16 C() : B() { } 17 private: 18 C(const C&); // 不可复制,C++98 风格 19 }; 20 21 int main() 22 { 23 A a1(7); 24 A a2(a1); // 调用复制构造函数 25 B b; 26 B b2 = b; 27 A a3 = b; // 转换到 A& 并调用复制构造函数 28 volatile A va(10); 29 // A a4 = va; // 编译错误 30 31 C c; 32 // C c2 = c; // 编译错误 33 }
五,移动构造函数
类名(类名 &&)
当(直接初始化或复制初始化)从同类型的右值初始化对象时,调用移动构造构造函数,包含
1)初始化T a =std::move(b);或T a(std::move(b)),b类型为T
2)函数实参传递f(std::move(a));其中a类型为T而f为Ret f(T f);
3)函数返回:在T f()的函数中 return a;其中啊类型为T,
隐式声明的移动构造函数
若不对类类型(struct、class 或 union)提供任何用户定义的移动构造函数,且下列各项均为真:
- 没有用户声明的复制构造函数;
- 没有用户声明的复制赋值运算符;
- 没有用户声明的移动复制运算符;
- 没有用户声明的析构函数;
则编译器将声明一个移动构造函数,作为其类的非 explicit的 inline public
成员,签名为 T::T(T&&)
。
类可以拥有多个移动构造函数,例如 T::T(const T&&) 和 T::T(T&&)。当存在用户定义的移动构造函数时,用户仍可用关键词 default
强制编译器生成隐式声明的移动构造函数。
1 #include <string> 2 #include <iostream> 3 #include <iomanip> 4 #include <utility> 5 6 struct A 7 { 8 std::string s; 9 int k; 10 A() : s("test"), k(-1) { } 11 A(const A& o) : s(o.s), k(o.k) { std::cout << "move failed!\n"; } 12 A(A&& o) noexcept : 13 s(std::move(o.s)), // 类类型成员的显式移动 14 k(std::exchange(o.k, 0)) // 非类类型成员的显式移动 15 { } 16 }; 17 18 A f(A a) 19 { 20 return a; 21 } 22 23 struct B : A 24 { 25 std::string s2; 26 int n; 27 // 隐式移动构造函数 B::(B&&) 28 // 调用 A 的移动构造函数 29 // 调用 s2 的移动构造函数 30 // 并进行 n 的逐位复制 31 }; 32 33 struct C : B 34 { 35 ~C() { } // 析构函数阻止隐式移动构造函数 C::(C&&) 36 }; 37 38 struct D : B 39 { 40 D() { } 41 ~D() { } // 析构函数阻止隐式移动构造函数 D::(D&&) 42 D(D&&) = default; // 强制生成移动构造函数 43 }; 44 45 int main() 46 { 47 std::cout << "Trying to move A\n"; 48 A a1 = f(A()); // 按值返回时,从函数形参移动构造其目标 49 std::cout << "Before move, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; 50 A a2 = std::move(a1); // 从亡值移动构造 51 std::cout << "After move, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; 52 53 std::cout << "Trying to move B\n"; 54 B b1; 55 std::cout << "Before move, b1.s = " << std::quoted(b1.s) << "\n"; 56 B b2 = std::move(b1); // 调用隐式移动构造函数 57 std::cout << "After move, b1.s = " << std::quoted(b1.s) << "\n"; 58 59 std::cout << "Trying to move C\n"; 60 C c1; 61 C c2 = std::move(c1); // 调用复制构造函数 62 63 std::cout << "Trying to move D\n"; 64 D d1; 65 D d2 = std::move(d1); 66 }
六,析构函数
是对象生存期终结时调用的特殊成员函数,目的时释放对象可能在其生成期间获得的资源
~类名();virtual ~类名()虚析构函数在基类中常为必要的
生存期结束包含:1)程序终止,对于静态存储的对象;2)退出线程,对于具有线程局部存储的对象
3)作用域结束,对于具有自动存储期的对象和生存期因绑定到引用而延长的临时量;
4)delete 表达式,对于具有动态存储的对象5)完整表达式的结尾,对于无名临时量;
6)栈回朔,对于具有自动存储期的对象,当未捕捉的异常脱离其块时;
虚析构函数:通过指向基类指针删除对象引发未定义行为,除非基类的析构函数为虚函数
1 class Base { 2 public: 3 virtual ~Base() {} 4 }; 5 class Derived : public Base {}; 6 Base* b = new Derived; 7 delete b; // 安全
纯虚构函数:析构函数可以声明为纯虚,例如对于需要声明为抽象类,但没有其他可声明为纯虚的适合函数的基类。这种析构函数必须有定义,因为在销毁派生类时,所有基类析构函数都总是得到调用:
1 class AbstractBase { 2 public: 3 virtual ~AbstractBase() = 0; 4 }; 5 AbstractBase::~AbstractBase() {} 6 class Derived : public AbstractBase {}; 7 // AbstractBase obj; // 编译错误 8 Derived obj; // OK
七,复制赋值运算符
类名 &
类名 ::
operator=
( 类名 )
若不对类类型(struct、class 或 union)提供任何用户定义的复制赋值运算符,则编译器将始终声明一个,作为类的 inline public 成员。当以下各项均为真时,这个隐式声明的复制赋值运算符拥有形式 T& T::operator=(const T&):
T
的每个直接基类B
均拥有复制赋值运算符,其形参是 B 或 const B& 或 const volatile B&;T
的每个类类型或类数组类型的非静态数据成员M
均拥有复制赋值运算符,其形参是 M 或 const M& 或 const volatile M&。
否则隐式声明的复制赋值运算符被声明为 T& T::operator=(T&)。(注意因为这些规则,隐式声明的复制赋值运算符不能绑定到 volatile 左值实参。)
八.移动赋值运算符
类名 &
类名 :: operator=
( 类名 &&
)
若不对类类型(struct、class 或 union)提供任何用户定义的移动赋值运算符,且下列各项均为真:
- 没有用户声明的复制构造函数;
- 没有用户声明的移动构造函数;
- 没有用户声明的复制赋值运算符;
- 没有用户声明的析构函数,
则编译器将声明一个移动赋值运算符,作为其类的 inline public
成员,并拥有签名 T& T::operator=(T&&)
。
类可以拥有多个移动赋值运算符,如 T& T::operator=(const T&&) 和 T& T::operator=(T&&)。当存在用户定义的移动赋值运算符时,用户仍可用关键词 default
强迫编译器生成隐式声明的移动赋值运算符。