C++ ctor & dtor

2022-12-27

构造函数

  1. virtual构造函数 仅有抽象类的构造函数(纯虚函数)才能声明为virtual,因为虚函数存储在内存中,vtable在构造时建立,此时无法找到vtable
  2. private构造函数

类的构造函数为private时,只能使用类内的static成员函数构造这个类,通常用于单例模式,注意如果把拷贝构造也声明为private,则无法使用对该类使用值传递

  1. const构造函数

不允许,因为构造函数必须修改类中成员,而const不允许成员函数修改成员变量, 其本质是类的所有成员函数都隐藏着一个const类型的this指针, 构造函数不允许传入const this.

  1. constexpr构造函数

仅用于全基础类型变量赋值的构造函数,且必须全部赋值

  1. 拷贝构造函数

实例值传递传参、值传递返回、值传递构造另一实例时调用,默认浅拷贝

  1. 移动构造函数

移动构造函数是参数类型为右值引用的拷贝构造函数,需要手动调用(使用std::move()),原来的实例不再使用时,没必要拷贝一个原实例的副本,而是直接“夺舍”, 默认移动构造函数如果没有通过=default生成的话,只有在没有自定义拷贝构造函数、没有自定义operator=也没有自定义析构函数的时候才会自动生成,最后一个很容易被忽略。另外如果是POD之类的简单类型,移动构造函数可能跟拷贝构造函数是同一个实现。。

  1. user-provided constructor 在提供带参数的 user-provided constructor时, 构造函数后加 =default可以强迫编译器生成默认函数, 叫做显式缺省

类内初始化

构造函数列表初始化和类内初始化顺序是成员定义顺序而非列表顺序

另外类内初始化时机是创建对象时,故非const的static对象不允许类内初始化

析构函数

  1. virtual析构函数 保证实例始终调用子类自己的析构函数,而非父类的。
  2. private析构函数 保证只能在堆上创建实例,且此时不能使用普通的delete进行删除,而是必须在成员函数中进行析构,因为外部无法访问析构函数,在栈上创建时直接编译失败
  3. const析构函数 保证析构函数中不对成员变量进行修改

C++ 不支持static, const构造和析构函数

隐式类型转换

  • 多字节类型转化为少字节类型, 保证数据完整性
  • 子类转化为父类, 高精度转化为低精度

``` int a = 3; double b = 3.1; a + b // int 类型 // 高精度转为低精度 class A {}; class B: public A {}; void func(A& a); B b; func(b); // 子类隐式转为父类

class Test { public: Test(int a): m(a) {}    int m; } Test t = 1; // 先通过构造函数将1隐式转化为Test再拷贝构造 // 构造函数定义为explicit Test(int a): m(a) {} 则代码报错,因为禁止使用该构造函数进行隐式类型转换 ```

=default=delete

在C++中,声明自定义的类型之后,编译器会默认生成一些成员函数,这些函数被称为默认函数。其中包括

  1. (默认)构造函数
  2. 拷贝(复制)构造函数
  3. 拷贝(复制)赋值运算符
  4. 移动构造函数
  5. 移动赋值运算符
  6. 析构函数

另外,编译器还会默认生成一些操作符函数,包括

  1. operator ,
  2. operator &
  3. operator &&
  4. operator *
  5. operator ->
  6. operator ->*
  7. operator new
  8. operator delete

如果类设计者又实现了这些函数的自定义版本后,编译器就不会去生成默认版本, 会导致类不再是POD类型.

而声明时在函数后面加上 =default可以指示编译器生成该函数的默认版本.

=delete则是禁止该函数使用, 之前该功能常使用 private实现.

trivial, standard layout, aggregate, POD

该部分内容主要来自C++14 [class].9

Aggregate

C++14 8.5.1 Aggregate [dcl.init.aggr]:

Aggregate是一个数组或者是一个没有用户定义的构造函数、没有private和protected的非静态成员变量、没有基类和虚函数的类。

An aggregate is an array or a class (clause 9) with no user-declared constructors (12.1), no private or protected non-static data members (clause 11), no base classes (clause 10), and no virtual functions (10.3).

在C++中,class术语指的是所有的类、结构体和联合体。所以说,一个类(或者结构体,或者联合体)满足上述定义的条件时,就是aggregate类型的。这些条件意味着什么?

  • 这并不意味着aggregate类不能拥有构造函数,事实上它可以拥有默认的构造函数和/或赋值构造函数,只要它们是编译器隐式声明,而不是用户显示声明;
  • 没有private和protected的非静态成员变量,你可以定义很多private和protected的成员函数(构造函数除外)和private和protected的静态成员变量,这都不违背aggregate的规则;
  • aggregate类可以拥有用户声明/用户定义的赋值操作和/或析构函数;
  • 数组都是aggregate类型,即便数组元素是非aggregate类型。

特性:

8.5.1:

When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.

If the initializer-clause is an expression and a narrowing conversion (8.5.4) is required to convert the expression, the program is ill-formed.

[ Note: If an initializer-clause is itself an initializer list, the member is list-initialized, which will result in a recursive application of the rules in this section if the member is an aggregate. — end note ]

使用列表初始化aggregate时, 列表中的每个元素都被当做一个成员的 initializer, 使用拷贝构造按顺序构造aggregate的成员, 而一旦出现截断(double -> int)或表达式, 将造成ill-formed

当一个类,存在非静态的类内成员初始化,那么这个类就不是一个聚类(C++11, C++14删除)

```

include

include

using namespace std;

/ -------------------------------------------- / class A { public:    A() = default;    A(int x): a(x) { }    A(initializer_list) = delete;    int a; };

int main() {    // A a = {1}; error: use of deleted function \A::A(std::initializer_list)\        cout << is_aggregate::value << endl; // 输出0 } / -------------------------------------------- / class A { public:    A() = default;    A(initializer_list) = delete;    int a; };

int main() {    A a = {1};    cout << is_aggregate::value << endl; // 输出1, 可以列表初始化. } ```

注意: C++17貌似有变, 有基类函数也可以成为聚类, 且不调用列表初始化函数.

```

include

include

using namespace std;

class B {    int c; }; class A : public B { public:    A() = default;    A(initializer_list) = delete;    int a; };

int main() {    B b;    A x = {b, 1};    cout << is_aggregate::value << endl; } // g++ -std=c++17可以编译过且输出1. ```

trivial

trivial意思是无意义/平凡,这个trivial和non-trivial是对类的四种函数来说的:

  • 构造函数(ctor)
  • 复制构造函数(copy)
  • 移动构造函数(move)
  • 拷贝赋值操作符(assignment)
  • 移动赋值操作符(move assignment)
  • 析构函数(dtor)

对除构造函数以外的函数/操作符, 如没有 user-provided以上的函数, 则称该函数(copy, assignment, dtor)为平凡函数, 而对构造函数(ctor)来说, 如果用户提供了自定义的构造函数则不会生成默认构造函数, 但用户可以使用 =default声明来强迫编译器生成平凡的构造函数, 不平凡函数的意义就是处理用户自己产生的一些事务, 比如为指针申请内存/释放内存.可平凡复制类可以按字节拷贝.

维护虚函数、虚基类所需的额外信息,在对象生存期间是不可改变的。然而直接从另一同类子对象逐位复制可能导致这些信息被修改。所以复制/移动构造函数、赋值运算符必须正确处理这些信息。

类似地默认构造函数必须写入这些信息,故它无法等同于无操作。

trivially_copyable: 拥有平凡的拷贝构造, 移动构造和析构函数, 或者说 trivial是在满足 trivially_copyable的同时, 必须拥有非用户定义的构造函数(不自定义构造函数或使用 =default).

注意: trivial类型不得在类内初始化值, 因为给类成员初始值相当于一种用户操作.

C++14 class.9:

A trivially copyable class is a class that: — has no non-trivial copy constructors (12.8), — has no non-trivial move constructors (12.8), — has no non-trivial copy assignment operators (13.5.3, 12.8), — has no non-trivial move assignment operators (13.5.3, 12.8), and — has a trivial destructor (12.4). A trivial class is a class that has a trivial default constructor (12.1) and is trivially copyable. [ Note: In particular, a trivially copyable or trivial class does not have virtual functions or virtual base classes.—end note ]

标准布局

C++14 class.9:

A standard-layout class is a class that: — has no non-static data members of type non-standard-layout class (or array of such types) or reference, ** — has no virtual functions (10.3) and no virtual base classes (10.1), — has the same access control (Clause 11) for all non-static data members, ** — has no non-standard-layout base classes, — either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and—has no base classes of the same type as the first non-static data member.

标准布局须满足以下条件(类地址等于第一个成员的地址)

  1. 没有引用类型的非静态数据成员
  2. 没有虚函数和虚基类 生成虚表
  3. 所有非静态成员符合标准布局
  4. 所有非静态成员有相同访问权限 C++可能会按照不同的访问权限重新排序成员
  5. 在类或者结构体继承时,满足一下两种情况之一:

  6. 没有带有非静态数据成员的基类,或

  7. **最终派生类中没有非静态数据成员,且至多有一个基类拥有非静态数据成员 **

(在同一个类中声明所有非静态数据成员(全在派生类或全在某个基类)) 只要非静态成员同时出现在派生类中或者基类中,都不属于标准布局。而多重继承也会导致类型布局的一些变化,所以一旦非静态成员出现在多个基类中,派生类也不属于标准布局。 C++标准并不强制规定跨类成员顺序,虽然实际上可能是标准布局 6. 类中第一非静态成员的类型与其基类不同

class A {}; class B : public A { public:    A a;    int mem; }; // 此时class B不是标准布局

C++标准规定同一类型不同变量须有不同地址, 所以会给 class填充一个字节避免连续定义空类时违背该条款

空基类优化:https://www.apiref.com/cpp-zh/cpp/language/ebo.html

POD Plain Old Data

C++11之前POD是POD结构体或POD联合体, 所有的POD类都必须是Aggregate,或者说,一个类如果不是Aggregate,则一定不是POD类

C++ 98/03:

将对象的各字节拷贝到一个字节数组中,然后再将它重新拷贝到原先的对象所占的存储区中,此时该对象应该具有它原来的值。

A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined copy assignment operator and no user-defined destructor.

**Similarly, a POD-union is an aggregate union that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined copy assignment operator and no user-defined destructor. **

A POD class is a class that is either a POD-struct or a POD-union.

An aggregate class is called a POD if it has no user-defined copy-assignment operator and destructor and none of its nonstatic members is a non-POD class, array of non-POD, or a reference.

C++14 class.9:

A POD struct is a non-union class that is both a trivial class and a standard-layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types).

Similarly, a POD union is a union that is both a trivial class and a standard-layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types). A POD class is a class that is either a POD struct or a POD union.

注意, C++11 放宽了POD的标准, 在C++11之前, POD是aggregate子集

在C++11之前相对于POD, aggregate可以有以下的特点如C++标准所述

  • 用户定义的析构函数
  • 用户定义的拷贝构造函数
  • non-POD的成员

C++11之后

POD是符合以下三个条件的结构体或联合体

  1. 类型是平凡的(trivial)
  2. 内存布局是标准布局(standard layout)
  3. 没有非POD非静态成员

这意味着POD可以拥有基类, 且拥有基类的POD不再是aggregate类型

总结

  1. Aggregate 聚合概念是为了使用列表初始化时可以直接使用列表中的元素进行拷贝构造, 而不是通过 T (std::initializer_list), 该概念强调初始化,所以限制了用户定义的构造函数,限定 public, 无基类和虚函数,aggregate可以使用{}对其全部成员(数组元素)进行依次初始化, 或者说一旦突破以上的限制, 则大括号(brace)可能不足以对其进行初始化, 此时的列表初始化会采用默认生成的列表初始化函数.聚合类型是字面量类型. 注意: 大括号初始化不调用任何构造函数, 而基类在内存布局上属于派生类成员, 所以一旦有基类则基类构造函数不会被调用. A(initializer_list<int>) = delete;非聚合类, 删除列表初始化函数后无法构造.
  2. trivial 平凡类意味着不做用户自定义的操作, 平凡, 即和大众一样, 编译器可以用类似的操作对平凡类进行操作(构造, 拷贝, 移动, 析构, 赋值, 移动赋值) 而 可平凡拷贝(is_trivially_copyable)则是除了构造以外平凡即可以进行平凡的拷贝, 但不能平凡地被构造. A trivial type T can be copied into an array of char or unsigned char, and safely copied back into a T variable.平凡类型占据连续内存, 可以在C++中使用内存复制而保证完整性(拷贝到字节数组再拷贝回来有相同值), 但无法在C语言中放心的使用这种类型, 平凡的含义也可以理解为对其内存进行操作. 或者说, 可以进行字节拷贝的前提条件是, 自动调用的函数不得含有用户指定的操作, 否则字节拷贝后这些操作会被遗忘. 另一个特性是, 静态存储区的变量最好是可平凡析构的
  3. Standard layout 标准类型布局的意义是得到和C语言相同的内存布局, 排除C语言中不存在的语言特性(虚表, 虚基类, 引用, 访问控制), 使其可以在C语言中使用, 同样是 memcopy-able. (仅限定内存布局相同, 这意味着不限定各种非虚函数)
  4. POD / Plain old data C++11之前 POD类型是 aggregate类型的子集, 需要

  5. 没有用户定义的拷贝构造

  6. 没有用户定义的析构函数
  7. 没有 non-POD成员

The memory layout of POD types is therefore contiguous and each member has a higher address than the member that was declared before it, so that byte for byte copies and binary I/O can be performed on these types. 内存布局连续且地址递减, 可以进行二进制IO和拷贝 5. 一句话总结 aggregate聚合体可以用{}初始化其所有成员/元素(类的内存布局中只存在能被用户访问的非静态成员) trivial平凡类可以使用同样类型字节对其进行初始化/拷贝(自动调用的函数不含用户指定的操作),不存在浅拷贝问题 standard layout标准布局类的数据可以被C语言放心使用(不含C++独占特性),可以使用memcpy进行拷贝,但可能出现浅拷贝或资源泄露。 POD类型具有平凡和标准布局特性, 可以进行二进制IO 注意: POD和标准类型的区别是, 标准内存布局类可以不平凡, 因为其构造/析构函数等不影响内存布局, 但 POD必须平凡

关于类型特征判定, 可以使用 type_traits中的下列模板判断, 是则值为1, 否则为0

```

include

std::is_aggregate::value (C++1) std::is_trivially_copyable::value std::is_trivial::value std::is_standard_layout::value std::is_pod::value https://blog.csdn.net/qq811299838/article/details/100626827 ```

newdelete

  • new有两种: 关键字 new,::operator newT::new new操作符: 不能被重载, 被翻译为operator new + 构造函数, operator new分为全局和类重载, 优先调用类重载形式, 只分配空间
  • new的六种重载 https://en.cppreference.com/w/cpp/memory/new/operator_new

``` void *operator new(std::size_t count)    throw(std::bad_alloc);             //一般的版本

void *operator new(std::size_t count,  //兼容早版本的new    const std::nothrow_t&) throw();    //内存分配失败不会抛出异常

void operator new(std::size_t count, void ptr) throw();                                       //placement版本 void *operator new  //    throw(std::bad_alloc);

void *operator new throw();

void operator new throw(); ``` * placement new*

``` class Test { public:    Test() {        cout << \"ctor\" << endl;   }    ~Test() {        cout <<\"dtor\" << endl;   } };

int main() {    void *t = malloc(sizeof(Test));    new (t) Test();    t->~Test();    free(t); } ```

https://www.cnblogs.com/lemaden/p/10150665.html

https://blog.csdn.net/linuxheik/article/details/80449059

initializer_list

为非aggregate类型提供列表初始化, initializer_list<T> lst = {...}

例如 std::vector等container提供了vector<T>(initializer<T> il)

C++17的更改

微软相关文档

内存布局