当前位置:网站首页>本周小贴士#134:make_unique与私有构造函数
本周小贴士#134:make_unique与私有构造函数
2022-07-07 15:39:00 【-飞鹤-】
作为totw#134最初发表于2017年5月10日
由谷歌工程师Yitzhak Mandelbaum创作
因此,你阅读了小贴士#126并准备留下一些新的东西。一切都是正常的,直到你尝试使用absl::make_unique并使用私有构造函数去构造对象,但是编译失败。让我们看一下这个问题的一个具体示例,以便理解哪里出了问题。然后,我们可以讨论一些解决方案。
示例:制造小部件
你正在定义一个类来展示小部件。每个小部件都有一个标识符,这些标识符受某些约束。为了确保一直满足这些约束,你将Widget类的构造函数声明为私有,并为用户提供工厂函数Make,用于生成具有合适标识符的小部件。(有关为什么工厂函数优于初始化方法的建议,请参阅技巧 #42。)
class Widget {
public:
static std::unique_ptr<Widget> Make() {
return absl::make_unique<Widget>(GenerateId());
}
private:
Widget(int id) : id_(id) {
}
static int GenerateId();
int id_;
}
当你尝试编译时,你将得到如下的错误:
error: calling a private constructor of class 'Widget'
{
return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
^
note: in instantiation of function template specialization
'absl::make_unique<Widget, int>' requested here
return absl::make_unique<Widget>(GenerateId());
^
note: declared private here
Widget(int id) : id_(id) {
}
当make已经访问私有构造函数时,absl::make_unique无法工作!注意,这个问题也出现在友元下。例如:Widget的友元使用absl::make_unique去构造Widget会有同样的问题。
建议
我们推荐以下的任一个替代方案:
- 使用new和absl::WrapUnique,但请解释你的选择。例如:Widget的友元使用absl
// 使用new访问非仅有构造函数 return absl::WrapUnique(new Widget(...)); - 考虑构造函数是否可以安全地公开。如果是这样,那么在适合直接构造时将其公开并记录。
在很多情况下,将构造函数标记为私有是过度设计。在这些情况下,最好的解决方案是将你的构造函数标记为公有并记录它们的正确使用。然而,如果你的构造函数需要设置为私有(比如说,为了确保类不变量),则使用new和WrapUnique。
为什么我不能友元absl::make_unique?
你可能想友元absl::make_unique,这样它就可以访问你的私有构造函数。这是一个糟糕的主意,有几个原因:
首先,虽然对友元实践的全面讨论超过出本贴士的范围,但是一个好的经验法则是“不要远距离的朋友关系”。否则,你将创建一个友元竞争声明,一个不由所有者维护的声明。另外请参见风格指南建议。
其次,请注意,你依赖于absl::make_unique的实现细节,即它直接调用new。如果它被重构以间接调用new——例如,在构造C++14或更高版本中,absl::make_unique是std::make_unique的别名,并且友元声明是无效的。
最后,通过友元absl::make_unique,你允许任何人以这种方式创建对象,那么为什么不将你的构造函数声明为公有,然后完全避免这个问题呢?
std::shared_ptr呢?
对于std::shared_ptr,情况有些不同。此处没有absl::WrapSahred和模拟(std::shared_ptr(new T(…)))涉及两个分配,其中std::make_shared可以用一个来完成。如果这种差异很重要,那么请考虑习惯用法:让构造函数采用只有特定代码才能创建的特殊标记。例如:
class Widget {
class Token {
private:
Token() {
}
friend Widget;
};
public:
static std::shared_ptr<Widget> Make() {
return std::make_shared<Widget>(Token{
}, GenerateId());
}
Widget(Token, int id) : id_(id) {
}
private:
static int GenerateId();
int id_;
};
关于习惯用法,参考以下文章:
More Useful Empty Classes
Passkey Idiom and Better Friendship in C++
边栏推荐
- 【TPM2.0原理及应用指南】 16、17、18章
- 如何在博客中添加Aplayer音乐播放器
- LeetCode 1696. Jumping game VI daily question
- mysql实现两个字段合并成一个字段查询
- The process of creating custom controls in QT to encapsulating them into toolbars (II): encapsulating custom controls into toolbars
- Test case management tool recommendation
- LeetCode 213. Home raiding II daily question
- 数值 - number(Lua)
- 防火墙系统崩溃、文件丢失的修复方法,材料成本0元
- 服务器彻底坏了,无法修复,如何利用备份无损恢复成虚拟机?
猜你喜欢

Sator推出Web3游戏“Satorspace” ,并上线Huobi

【TPM2.0原理及应用指南】 16、17、18章

科普达人丨一文弄懂什么是云计算?

Seaborn data visualization

鲲鹏开发者峰会2022 | 麒麟信安携手鲲鹏共筑计算产业新生态

责任链模式 - Unity

Lex & yacc of Pisa proxy SQL parsing

SlashData开发者工具榜首等你而定!!!

状态模式 - Unity(有限状态机)

With the latest Alibaba P7 technology system, mom doesn't have to worry about me looking for a job anymore
随机推荐
How to add aplayer music player in blog
Seaborn数据可视化
Jenkins发布uniapp开发的H5遇到的问题
邮件服务器被列入黑名单,如何快速解封?
[source code interpretation] | source code interpretation of livelistenerbus
Problems encountered in Jenkins' release of H5 developed by uniapp
LeetCode 1049. Weight of the last stone II daily question
Notes on installing MySQL in centos7
LeetCode 497(C#)
mysql官网下载:Linux的mysql8.x版本(图文详解)
The top of slashdata developer tool is up to you!!!
国内首创!Todesk将RTC技术融入远程桌面,画质更清晰操作更流畅
【源码解读】| LiveListenerBus源码解读
【Seaborn】组合图表、多子图的实现
【TPM2.0原理及应用指南】 16、17、18章
【视频/音频数据处理】上海道宁为您带来Elecard下载、试用、教程
Rpcms method of obtaining articles under the specified classification
到底有多二(Lua)
命令模式 - Unity
PLC: automatically correct the data set noise, wash the data set | ICLR 2021 spotlight