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 强迫编译器生成隐式声明的移动赋值运算符。

 

posted @ 2021-07-17 20:35  刘允朵的代码屋  阅读(496)  评论(0编辑  收藏  举报