六一的部落格


关关难过关关过,前路漫漫亦灿灿。



constructor

妥善初始化类对象的每一个数据成员

与类同名, 无返回类型

不能声明为const

支持重载

1class T
2{
3public:
4    T();
5};

内置类型和复合类型视情况分合


定义构造函数

 1struct Sales_data
 2{
 3    Sales_data(istream &);
 4    Sales_data() {}                    // 类内定义
 5};
 6
 7Sales_data::Sales_data(istream &is)    // 类外定义
 8{
 9    read(is, *this);
10}

const对象的创建

当我们构建一个具有顶层const的对象, 构造函数结束后,对象才具有顶层const

构造函数在const对象的构造过程中可以向其写值


构造函数和数据成员初始化

建议

  1. 如果数据成员满足以下条件, 建议为它们提供类内初始值, 或者在初始值列表中对其初始化
    • 内置类型
    • 具有顶层const
    • 为左值引用
  2. 对构造函数传参时, 实参一般不同于数据成员的类内初始值

在函数体执行之前, 已完成数据成员初始化. 按照数据成员在类中的出现顺序

函数体内对数据成员使用赋值运算符, 均为为赋值操作


默认构造函数

形参列表为空; 或者每个形参都有默认值的构造函数

默认初始化类对象时调用


如果类没有定义任何构造函数, 编译器会提供默认构造函数的合成版本

如果默认构造函数的合成版本非删除 delete :

此时无初始值列表

数据成员初始化方式:

  1. 有类内初始值
  2. 无类内初始值, 默认初始化数据成员

如果在类中定义了其他的构造函数, 可以显式要求编译器提供默认构造函数的合成版本

  1. 默认构造函数的合成版本为内联

    1class T
    2{
    3public:
    4    T() = default;
    5};
  2. 默认构造函数的合成版本非内联

    1class T
    2{
    3public:
    4    T();
    5};
    6
    7T::T() = default;

显式要求编译器提供默认构造函数的合成版本为 =default , 仍可能得到一个删除 delete 的默认构造函数的合成版本

 1#include <iostream>
 2
 3using namespace std;
 4
 5class test
 6{
 7public:
 8    test(int aa) : a(aa) {}
 9    int a;
10};
11
12class Foo
13{
14public:
15    Foo() = default;
16    void PrintFoo() { cout << a; }
17
18private:
19    int a;
20    test t;
21};
22
23int main()
24{
25    return 0;
26}

给出告警

warning: explicitly defaulted default constructor is implicitly deleted [-Wdefaulted-function-deleted]
Foo() = default;
^

note: default constructor of 'Foo' is implicitly deleted because field 't' has no default constructor
test t;
^
1 warning generated.

编译器提供非删除 delete 的默认构造函数的合成版本的前提

所有类类型数据成员均可默认初始化, 即所属类拥有默认构造函数

或者, 对于那些没有默认构造函数的类类型数据成员,给出类内初始值


默认构造函数的合成版本为删除

delete

两种情形:

  1. 显式删除: 要求编译器如此
  2. 隐式删除: 存在数据成员无类内初始值, 又无法默认初始化的情形; 编译器提供的默认构造函数的合成版本为隐式删除 implicitly-delete

构造函数初始值列表

在定义时给出

使用给定值初始化数据成员

建议与数据成员在类中的出现顺序一致


委托构造函数

delegating constructor

初始值列表有且只有一个同名构造函数作为入口, 代为初始化对象:

  1. 初始值列表中不初始化数据成员
  2. 在初始值列表中调用其他构造函数, 且只调用一个

先执行被委托的构造函数的初始值列表和函数体. 之后把控制权交还给委托者的函数体

被委托的构造函数通常在初始值列表中初始化了所有数据成员,委托者只传递自己有的参数,其他数据成员使用默认值


示例

  1. 多个委托构造函数

     1class Sales_data
     2{
     3public:
     4    Sales_data(string s, unsigned cnt, double price) : bookNo(s), units_sold(cnt), revenue(cnt*price) {}
     5
     6    // 以下均是委托构造函数
     7
     8    // 默认构造函数也可以是委托构造函数
     9    Sales_data() : sales_data("", 0, 0) {}
    10
    11    Sales_data(string s) : sales_data(s, 0, 0) {}
    12
    13    Sales_data(istream &is) : Sales_data() { read(is, *this); }
    14};
  2. 构造函数和被构造函数的调用顺序

     1#include <iostream>
     2#include <string>
     3
     4using std::cout;
     5using std::endl;
     6using std::ostream;
     7using std::string;
     8
     9class GamePlayer
    10{
    11    friend ostream &operator<<(ostream &os, const GamePlayer &gp);
    12
    13public:
    14    GamePlayer(string _name, int _age) : name(_name), age(_age) { cout << "This is delegated constructor.\n"; } // 被委托的构造函数
    15    GamePlayer() : GamePlayer("", 0) { cout << "This is default constructor, which is delegating.\n"; }
    16    GamePlayer(string _name) : GamePlayer(_name, 0) { cout << "This is another delegating constructor.\n"; }
    17
    18private:
    19    int age;
    20    string name;
    21};
    22
    23ostream &operator<<(ostream &os, const GamePlayer &gp)
    24{
    25    if (gp.name == "")
    26        os << "none";
    27    else
    28        os << gp.name;
    29    os << '\t' << gp.age;
    30    return os;
    31}
    32
    33int main()
    34{
    35    GamePlayer gp;
    36    cout << gp << endl;
    37    GamePlayer gp2("Kate", 25);
    38    cout << gp2 << endl;
    39    GamePlayer gp3("Mary");
    40    cout << gp3 << endl;
    41    return 0;
    42}

    输出

    This is delegated constructor.
    This is default constructor, which is delegating.
    none	0
    This is delegated constructor.
    Kate	25
    This is delegated constructor.
    This is another delegating constructor.
    Mary	0

转换构造函数

converting constructor

只接受一个实参, 或者除第一个参数外均有默认实参的构造函数

实质是定义了一条从构造函数的参数类型向类类型转换的规则

如果未使用关键字explicit,该规则可用于一次隐式转换


只允许一次隐式转换

  1. 函数F接受A类型变量
    1void F(T a);
  2. A类型支持B到A的隐式转换
    1void A(const B &b);
  3. B类型支持C到B的隐式转换
    1void B(const C &c);
  4. 函数F可以接受B类型对象,不接受C类型对象
    1A a;
    2B b;
    3C c;
    4
    5F(a);   // 正确
    6F(b);   // 正确: 发生一次隐式转换, b转换为A类型对象
    7F(c);   // 错误: 隐式转换最多一次
    

explicit关键字

explicit只用于转换构造函数, 只在声明时给出

将转换构造函数声明为显式, 定义显式转换规则

要么显式调用转换构造函数,要么配合static_cast使用


不可用于隐式转换

  1. 函数F接受A类型变量
    1void F(T a);
  2. A类型支持B到A的显式转换
    1explicit void A(const B &b);
  3. 函数F不接受B类型对象
    1A a;
    2B b;
    3
    4F(a);                 // 正确
    5F(b);                 // 错误: 不支持B到A的隐式转换
    6F(static_cast<A>(b)); // 正确
    7F(A(b));              // 正确
    

举例

接受const char *的string的构造函数不是explicit的

接受容器大小的构造函数是explicit的


示例

 1class Sales_data
 2{
 3public:
 4    Sales_data() = default;
 5
 6    explicit Sales_data(const string &s) : bookNo(s) {}
 7    explicit Sales_data(istream &);
 8};
 9
10// 以下操作不被允许
11item.combine(null_book);
12item.combine(cin);
13
14// 可以使用显式转换
15item.combine(Sales_data(null_book));
16item.combine(static_cast<Sales_data>(cin));
17
18Sales_data item1(null_book);        // 正确,是直接初始化
19Sales_data item2 = null_book;       // 错误:不允许将explicit构造函数用于拷贝形式的初始化过程

类的构造函数


constructor

妥善初始化类对象的每一个数据成员

与类同名, 无返回类型

不能声明为const

支持重载

1class T
2{
3public:
4    T();
5};

内置类型和复合类型视情况分合


定义构造函数

 1struct Sales_data
 2{
 3    Sales_data(istream &);
 4    Sales_data() {}                    // 类内定义
 5};
 6
 7Sales_data::Sales_data(istream &is)    // 类外定义
 8{
 9    read(is, *this);
10}

const对象的创建

当我们构建一个具有顶层const的对象, 构造函数结束后,对象才具有顶层const

构造函数在const对象的构造过程中可以向其写值


构造函数和数据成员初始化

建议

  1. 如果数据成员满足以下条件, 建议为它们提供类内初始值, 或者在初始值列表中对其初始化
    • 内置类型
    • 具有顶层const
    • 为左值引用
  2. 对构造函数传参时, 实参一般不同于数据成员的类内初始值

在函数体执行之前, 已完成数据成员初始化. 按照数据成员在类中的出现顺序

函数体内对数据成员使用赋值运算符, 均为为赋值操作


默认构造函数

形参列表为空; 或者每个形参都有默认值的构造函数

默认初始化类对象时调用


如果类没有定义任何构造函数, 编译器会提供默认构造函数的合成版本

如果默认构造函数的合成版本非删除 delete :

此时无初始值列表

数据成员初始化方式:

  1. 有类内初始值
  2. 无类内初始值, 默认初始化数据成员

如果在类中定义了其他的构造函数, 可以显式要求编译器提供默认构造函数的合成版本

  1. 默认构造函数的合成版本为内联

    1class T
    2{
    3public:
    4    T() = default;
    5};
  2. 默认构造函数的合成版本非内联

    1class T
    2{
    3public:
    4    T();
    5};
    6
    7T::T() = default;

显式要求编译器提供默认构造函数的合成版本为 =default , 仍可能得到一个删除 delete 的默认构造函数的合成版本

 1#include <iostream>
 2
 3using namespace std;
 4
 5class test
 6{
 7public:
 8    test(int aa) : a(aa) {}
 9    int a;
10};
11
12class Foo
13{
14public:
15    Foo() = default;
16    void PrintFoo() { cout << a; }
17
18private:
19    int a;
20    test t;
21};
22
23int main()
24{
25    return 0;
26}

给出告警

warning: explicitly defaulted default constructor is implicitly deleted [-Wdefaulted-function-deleted]
Foo() = default;
^

note: default constructor of 'Foo' is implicitly deleted because field 't' has no default constructor
test t;
^
1 warning generated.

编译器提供非删除 delete 的默认构造函数的合成版本的前提

所有类类型数据成员均可默认初始化, 即所属类拥有默认构造函数

或者, 对于那些没有默认构造函数的类类型数据成员,给出类内初始值


默认构造函数的合成版本为删除

delete

两种情形:

  1. 显式删除: 要求编译器如此
  2. 隐式删除: 存在数据成员无类内初始值, 又无法默认初始化的情形; 编译器提供的默认构造函数的合成版本为隐式删除 implicitly-delete

构造函数初始值列表

在定义时给出

使用给定值初始化数据成员

建议与数据成员在类中的出现顺序一致


委托构造函数

delegating constructor

初始值列表有且只有一个同名构造函数作为入口, 代为初始化对象:

  1. 初始值列表中不初始化数据成员
  2. 在初始值列表中调用其他构造函数, 且只调用一个

先执行被委托的构造函数的初始值列表和函数体. 之后把控制权交还给委托者的函数体

被委托的构造函数通常在初始值列表中初始化了所有数据成员,委托者只传递自己有的参数,其他数据成员使用默认值


示例

  1. 多个委托构造函数

     1class Sales_data
     2{
     3public:
     4    Sales_data(string s, unsigned cnt, double price) : bookNo(s), units_sold(cnt), revenue(cnt*price) {}
     5
     6    // 以下均是委托构造函数
     7
     8    // 默认构造函数也可以是委托构造函数
     9    Sales_data() : sales_data("", 0, 0) {}
    10
    11    Sales_data(string s) : sales_data(s, 0, 0) {}
    12
    13    Sales_data(istream &is) : Sales_data() { read(is, *this); }
    14};
  2. 构造函数和被构造函数的调用顺序

     1#include <iostream>
     2#include <string>
     3
     4using std::cout;
     5using std::endl;
     6using std::ostream;
     7using std::string;
     8
     9class GamePlayer
    10{
    11    friend ostream &operator<<(ostream &os, const GamePlayer &gp);
    12
    13public:
    14    GamePlayer(string _name, int _age) : name(_name), age(_age) { cout << "This is delegated constructor.\n"; } // 被委托的构造函数
    15    GamePlayer() : GamePlayer("", 0) { cout << "This is default constructor, which is delegating.\n"; }
    16    GamePlayer(string _name) : GamePlayer(_name, 0) { cout << "This is another delegating constructor.\n"; }
    17
    18private:
    19    int age;
    20    string name;
    21};
    22
    23ostream &operator<<(ostream &os, const GamePlayer &gp)
    24{
    25    if (gp.name == "")
    26        os << "none";
    27    else
    28        os << gp.name;
    29    os << '\t' << gp.age;
    30    return os;
    31}
    32
    33int main()
    34{
    35    GamePlayer gp;
    36    cout << gp << endl;
    37    GamePlayer gp2("Kate", 25);
    38    cout << gp2 << endl;
    39    GamePlayer gp3("Mary");
    40    cout << gp3 << endl;
    41    return 0;
    42}

    输出

    This is delegated constructor.
    This is default constructor, which is delegating.
    none	0
    This is delegated constructor.
    Kate	25
    This is delegated constructor.
    This is another delegating constructor.
    Mary	0

转换构造函数

converting constructor

只接受一个实参, 或者除第一个参数外均有默认实参的构造函数

实质是定义了一条从构造函数的参数类型向类类型转换的规则

如果未使用关键字explicit,该规则可用于一次隐式转换


只允许一次隐式转换

  1. 函数F接受A类型变量
    1void F(T a);
  2. A类型支持B到A的隐式转换
    1void A(const B &b);
  3. B类型支持C到B的隐式转换
    1void B(const C &c);
  4. 函数F可以接受B类型对象,不接受C类型对象
    1A a;
    2B b;
    3C c;
    4
    5F(a);   // 正确
    6F(b);   // 正确: 发生一次隐式转换, b转换为A类型对象
    7F(c);   // 错误: 隐式转换最多一次
    

explicit关键字

explicit只用于转换构造函数, 只在声明时给出

将转换构造函数声明为显式, 定义显式转换规则

要么显式调用转换构造函数,要么配合static_cast使用


不可用于隐式转换

  1. 函数F接受A类型变量
    1void F(T a);
  2. A类型支持B到A的显式转换
    1explicit void A(const B &b);
  3. 函数F不接受B类型对象
    1A a;
    2B b;
    3
    4F(a);                 // 正确
    5F(b);                 // 错误: 不支持B到A的隐式转换
    6F(static_cast<A>(b)); // 正确
    7F(A(b));              // 正确
    

举例

接受const char *的string的构造函数不是explicit的

接受容器大小的构造函数是explicit的


示例

 1class Sales_data
 2{
 3public:
 4    Sales_data() = default;
 5
 6    explicit Sales_data(const string &s) : bookNo(s) {}
 7    explicit Sales_data(istream &);
 8};
 9
10// 以下操作不被允许
11item.combine(null_book);
12item.combine(cin);
13
14// 可以使用显式转换
15item.combine(Sales_data(null_book));
16item.combine(static_cast<Sales_data>(cin));
17
18Sales_data item1(null_book);        // 正确,是直接初始化
19Sales_data item2 = null_book;       // 错误:不允许将explicit构造函数用于拷贝形式的初始化过程