|
| 1 | +# 委托构造函数 |
| 2 | + |
| 3 | +委托构造是C++11中引入的语法糖, 通过简单的语法, 可以在不影响性能的情况下, 来避免过多重复代码的编写, 实现构造逻辑复用 |
| 4 | + |
| 5 | +| Book | Video | Code | X | |
| 6 | +| --- | --- | --- | --- | |
| 7 | +| [cppreference](https://en.cppreference.com/w/cpp/language/initializer_list.html#Delegating_constructor) / [markdown](https://github.com/Sunrisepeak/mcpp-standard/blob/main/book/src/cpp11/10-delegating-constructors.md) | [视频解读]() | [练习代码]() | | |
| 8 | + |
| 9 | +**为什么引入?** |
| 10 | + |
| 11 | +- 构造函数重载中, 避免重复代码的编写 |
| 12 | +- 方便代码的维护 |
| 13 | + |
| 14 | +## 一、基础用法和场景 |
| 15 | + |
| 16 | +### 复用构造逻辑 |
| 17 | + |
| 18 | +当一个类需要编写重载的构造函数时, 很容易造成大量的重复代码, 例如: |
| 19 | + |
| 20 | +```cpp |
| 21 | +class Account { |
| 22 | + string id; |
| 23 | + string name; |
| 24 | + string coin; |
| 25 | +public: |
| 26 | + |
| 27 | + Account(string id_) { |
| 28 | + id = id_; |
| 29 | + name = "momo"; |
| 30 | + coin = "0元"; |
| 31 | + } |
| 32 | + |
| 33 | + Account(string id_, string name_) { |
| 34 | + id = id_; |
| 35 | + name = name_; |
| 36 | + coin = "0元"; |
| 37 | + } |
| 38 | + |
| 39 | + Account(string id_, string name_, int coin_) { |
| 40 | + id = id_; |
| 41 | + name = name_; |
| 42 | + coin = std::to_string(coin_) + "元"; |
| 43 | + } |
| 44 | +}; |
| 45 | +``` |
| 46 | +
|
| 47 | +这里3个构造函数中的初始化代码, 很明显是重复了(实际的初始化可能要更复杂)。 有了**委托构造**的支持后, 通过**在构造函数成员初始化列表**的位置以` : Account(xxx) `的形式来委托其他更加完整实现的构造函数进行构造, 这样就可以只保留一份代码 |
| 48 | +
|
| 49 | +```cpp |
| 50 | +class Account { |
| 51 | + string id; |
| 52 | + string name; |
| 53 | + string coin; |
| 54 | +public: |
| 55 | +
|
| 56 | + Account(string id_) : Account(id_, "momo") { } |
| 57 | +
|
| 58 | + Account(string id_, string name_) : Account(id_, name_, 0) { } |
| 59 | +
|
| 60 | + Account(string id_, string name_, int coin_) { |
| 61 | + id = id_; |
| 62 | + name = name_; |
| 63 | + coin = std::to_string(coin_) + "元"; |
| 64 | + } |
| 65 | +}; |
| 66 | +``` |
| 67 | + |
| 68 | +上面的两个构造函数, 通过委托构造的方式, 最后都会转发到`Account(string id_, string name_, int coin_)` |
| 69 | + |
| 70 | +### 为什么更方便维护? |
| 71 | + |
| 72 | +可以假设, 如果上面货币的单位或名称需要修改时, 重复的代码实现不仅没有遵循复用原则, 而且修改构造逻辑时也要重复多次的修改, 提高了维护成本 |
| 73 | + |
| 74 | +而通过委托构造的方式, 把构造逻辑放到了一个地方, 这样修改和维护时也变的更加方便 |
| 75 | + |
| 76 | +例如, 我们需要把`元`改成`原石`时, 只要修改一次即可 |
| 77 | + |
| 78 | +```cpp |
| 79 | +class Account { |
| 80 | + // ... |
| 81 | + Account(string id_, string name_, int coin_) { |
| 82 | + //... |
| 83 | + //coin = std::to_string(coin_) + "元"; |
| 84 | + coin = std::to_string(coin_) + "原石"; |
| 85 | + } |
| 86 | +}; |
| 87 | +``` |
| 88 | +
|
| 89 | +### 和封装成一个init函数的区别 |
| 90 | +
|
| 91 | +一些朋友可能会想到, 如果把构造逻辑写成一个`init`函数, 不就是也可以实现代码复用的效果吗? 为什么还要搞一个新的写法, 作为特性添加到标准中. 是不是有点多余并且让C++变的更加复杂了 |
| 92 | +
|
| 93 | +```cpp |
| 94 | +class Account { |
| 95 | + // ... |
| 96 | +
|
| 97 | + init(string id_, string name_, int coin_) { |
| 98 | + id = id_; |
| 99 | + name = name_; |
| 100 | + coin = std::to_string(coin_) + "元"; |
| 101 | + } |
| 102 | +
|
| 103 | +public: |
| 104 | +
|
| 105 | + Account(string id_) { init(id_, "momo", 0); } |
| 106 | +
|
| 107 | + Account(string id_, string name_) { init(id_, name_, 0); } |
| 108 | +
|
| 109 | + Account(string id_, string name_, int coin_) { |
| 110 | + init(id_, name_, coin_); |
| 111 | + } |
| 112 | +}; |
| 113 | +``` |
| 114 | + |
| 115 | +实际, 从性能角度考虑。大多数时候, 单独封装一个`init`函数的性能是低于**委托构造**的。因为成员的构造, 一般会经历两个阶段: |
| 116 | + |
| 117 | +- 第一步: 执行 默认初始化 或 成员初始化列表 |
| 118 | +- 第二步: 运行构造函数体中的构造逻辑 |
| 119 | + |
| 120 | +```cpp |
| 121 | +class Account { |
| 122 | + // ... |
| 123 | +public: |
| 124 | + |
| 125 | + Account(string id_, string name_, int coin_) |
| 126 | + /* : 1 - 成员初始化列表 */ |
| 127 | + { |
| 128 | + // 2 - 执行构造函数的函数体 |
| 129 | + init(id_, name_, coin_); |
| 130 | + } |
| 131 | +}; |
| 132 | +``` |
| 133 | + |
| 134 | +这就导致使用init函数, 实际上成员被"初始化"了两次, 而**委托构造**可以通过**成员初始化列表**来避免这个问题 |
| 135 | + |
| 136 | +```cpp |
| 137 | +class Account { |
| 138 | + // ... |
| 139 | +public: |
| 140 | + |
| 141 | + Account(string id_, string name_, int coin_) |
| 142 | + : id { id_ }, name { name_ }, coin { std::to_string(coin_) + "元" } |
| 143 | + { |
| 144 | + // ... |
| 145 | + } |
| 146 | +}; |
| 147 | +``` |
| 148 | + |
| 149 | +## 二、注意事项 |
| 150 | + |
| 151 | +### 临时对象误会 |
| 152 | + |
| 153 | +在一些不使用委托构造的场景中, 一个构造函数体中调用另外一个构造函数, 他实际只是创建了一个临时对象 |
| 154 | + |
| 155 | +- 调用普通函数`init`: 初始化的是本对象的成员 |
| 156 | +- 调用另外一个构造函数: 在本对象外, 创建了一个新的临时对象 |
| 157 | + |
| 158 | +```cpp |
| 159 | +class Account { |
| 160 | + // ... |
| 161 | +public: |
| 162 | + |
| 163 | + Account(string id_, string name_) { |
| 164 | + Account(id_, name_, 0); // 创建的是临时对象 |
| 165 | + // init(id_, name_, 0); |
| 166 | + // this->Account(id_, name_, 0); // error |
| 167 | + } |
| 168 | + |
| 169 | + Account(string id_, string name_, int coin_) { |
| 170 | + id = id_; |
| 171 | + name = name_; |
| 172 | + coin = std::to_string(coin_) + "元"; |
| 173 | + } |
| 174 | +}; |
| 175 | + |
| 176 | +``` |
| 177 | +
|
| 178 | +### 不能重复初始化 |
| 179 | +
|
| 180 | +当使用**委托构造**时, 就不能使用初始化列表去初始化其他成员, 这样的限制可以避免重复的初始化, 保证了数据成员只会被初始化一次 |
| 181 | +
|
| 182 | +例如, 如果下面的语法被允许 `coin` 将会被初始化多次且可能会造成歧义 |
| 183 | +
|
| 184 | +```cpp |
| 185 | +class Account { |
| 186 | + // ... |
| 187 | +public: |
| 188 | +
|
| 189 | + Account(string id_) |
| 190 | + : Account(id_, "momo"), coin { "0元" } // error |
| 191 | + { |
| 192 | +
|
| 193 | + } |
| 194 | +
|
| 195 | +}; |
| 196 | +``` |
| 197 | + |
| 198 | +## 三、其他 |
| 199 | + |
| 200 | +- [交流讨论](https://forum.d2learn.org/category/20) |
| 201 | +- [mcpp-standard教程仓库](https://github.com/Sunrisepeak/mcpp-standard) |
| 202 | +- [教程视频列表](https://space.bilibili.com/65858958/lists/5208246) |
| 203 | +- [教程支持工具-xlings](https://github.com/d2learn/xlings) |
0 commit comments