在 C++ 里,关注一个对象的“生命周期”,意味着了解如何创建对象、拷贝(COPY)对象、移动(MOVE)对象以及如何销毁对象,首先来定义什么是 COPY 和 MOVE:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
string ident(string arg) // string passed by value (copied into arg)
{
    return arg; // return str ing (move the value of arg out of ident() to a caller)
}
int main ()
{
    string s1 {"Adams"}; // initialize str ing (constr uct in s1).
    s1 = indet(s1); // copy s1 into ident()
    // move the result of ident(s1) into s1;
    // s1’s value is "Adams".
    string s2 {"Pratchett"}; // initialize str ing (constr uct in s2)
    s1 = s2; // copy the value of s2 into s1
    // both s1 and s2 have the value "Pratchett".
}

这个例子用到了多个函数:

  1. s1和s2用字符串常量初始化string的构造函数
  2. 传值的函数入参做了 copy
  3. 返回时将结果 move 到一个临时变量
  4. move 临时变量到 s1
  5. s2 拷贝赋值到 s1
  6. 析构函数释放所有资源

总结一个通用的类 X 需要下面这些构造函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class X {
    X(Sometype);             // ordinary constructor: create an object
    X();                     // default constructor
    X(const X &);            // copy constructor
    X(X &&);                 // move constructor
    X &operator=(const X &); // copy assignment: clean up target and copy
    X &operator=(X &&);      // move assignment: clean up target and move
    ˜X();                    // destructor: clean up
    // ...
};

构造函数和析构函数

  • 构造函数中常常要保证一些 invariant(不变式),类似于 size 必须严格和类中的数量相等。
  • 构造函数可以很好的与继承关系配合,自顶向下的创建对象,沿着 基类->成员->自身 的顺序进行构造。析构则反之。
  • 在内存区域显式管理内存时可能会手动调用构造/析构函数,例如 vector 的 push_back 和 pop_back,会进行放置式 new。
  • 基类的析构函数应当被声明为 virtual。

类对象初始化

  • Init Without Constructors
    • 基础类型直接初始化
    • 无构造函数类可以用 逐成员、copy、default init
    • 要注意默认类型只声明的时候是不初始化的
  • Init Using Constructors
    • 显示定义了一个构造函数后,默认构造函数就消失了,但是拷贝构造函数依然有默认实现
    • 推荐用 {} 初始化,而不是 ()、=,{} 不允许窄化转换
    • constructor 可以用 std::initializer_list 作为参数做初始化,要保证相互之间是同构的(homogeneous)