委托构造函数
委托构造是C++11中引入的语法糖, 通过简单的语法, 可以在不影响性能的情况下, 来避免过多重复代码的编写, 实现构造逻辑复用
Book | Video | Code | X |
---|---|---|---|
cppreference / markdown | 视频解读 | 练习代码 |
为什么引入?
- 构造函数重载中, 避免重复代码的编写
- 方便代码的维护
一、基础用法和场景
复用构造逻辑
当一个类需要编写重载的构造函数时, 很容易造成大量的重复代码, 例如:
class Account {
string id;
string name;
string coin;
public:
Account(string id_) {
id = id_;
name = "momo";
coin = "0元";
}
Account(string id_, string name_) {
id = id_;
name = name_;
coin = "0元";
}
Account(string id_, string name_, int coin_) {
id = id_;
name = name_;
coin = std::to_string(coin_) + "元";
}
};
这里3个构造函数中的初始化代码, 很明显是重复了(实际的初始化可能要更复杂)。 有了委托构造的支持后, 通过在构造函数成员初始化列表的位置以: Account(xxx)
的形式来委托其他更加完整实现的构造函数进行构造, 这样就可以只保留一份代码
class Account {
string id;
string name;
string coin;
public:
Account(string id_) : Account(id_, "momo") { }
Account(string id_, string name_) : Account(id_, name_, 0) { }
Account(string id_, string name_, int coin_) {
id = id_;
name = name_;
coin = std::to_string(coin_) + "元";
}
};
上面的两个构造函数, 通过委托构造的方式, 最后都会转发到Account(string id_, string name_, int coin_)
为什么更方便维护?
可以假设, 如果上面货币的单位或名称需要修改时, 重复的代码实现不仅没有遵循复用原则, 而且修改构造逻辑时也要重复多次的修改, 提高了维护成本
而通过委托构造的方式, 把构造逻辑放到了一个地方, 这样修改和维护时也变的更加方便
例如, 我们需要把元
改成原石
时, 只要修改一次即可
class Account {
// ...
Account(string id_, string name_, int coin_) {
//...
//coin = std::to_string(coin_) + "元";
coin = std::to_string(coin_) + "原石";
}
};
和封装成一个init函数的区别
一些朋友可能会想到, 如果把构造逻辑写成一个init
函数, 不就是也可以实现代码复用的效果吗? 为什么还要搞一个新的写法, 作为特性添加到标准中. 是不是有点多余并且让C++变的更加复杂了
class Account {
// ...
init(string id_, string name_, int coin_) {
id = id_;
name = name_;
coin = std::to_string(coin_) + "元";
}
public:
Account(string id_) { init(id_, "momo", 0); }
Account(string id_, string name_) { init(id_, name_, 0); }
Account(string id_, string name_, int coin_) {
init(id_, name_, coin_);
}
};
实际, 从性能角度考虑。大多数时候, 单独封装一个init
函数的性能是低于委托构造的。因为成员的构造, 一般会经历两个阶段:
- 第一步: 执行 默认初始化 或 成员初始化列表
- 第二步: 运行构造函数体中的构造逻辑
class Account {
// ...
public:
Account(string id_, string name_, int coin_)
/* : 1 - 成员初始化列表 */
{
// 2 - 执行构造函数的函数体
init(id_, name_, coin_);
}
};
这就导致使用init函数, 实际上成员被"初始化"了两次, 而委托构造可以通过成员初始化列表来避免这个问题
class Account {
// ...
public:
Account(string id_, string name_, int coin_)
: id { id_ }, name { name_ }, coin { std::to_string(coin_) + "元" }
{
// ...
}
};
二、注意事项
临时对象误会
在一些不使用委托构造的场景中, 一个构造函数体中调用另外一个构造函数, 他实际只是创建了一个临时对象
- 调用普通函数
init
: 初始化的是本对象的成员 - 调用另外一个构造函数: 在本对象外, 创建了一个新的临时对象
class Account {
// ...
public:
Account(string id_, string name_) {
Account(id_, name_, 0); // 创建的是临时对象
// init(id_, name_, 0);
// this->Account(id_, name_, 0); // error
}
Account(string id_, string name_, int coin_) {
id = id_;
name = name_;
coin = std::to_string(coin_) + "元";
}
};
不能重复初始化
当使用委托构造时, 就不能使用初始化列表去初始化其他成员, 这样的限制可以避免重复的初始化, 保证了数据成员只会被初始化一次
例如, 如果下面的语法被允许 coin
将会被初始化多次且可能会造成歧义
class Account {
// ...
public:
Account(string id_)
: Account(id_, "momo"), coin { "0元" } // error
{
}
};