当前位置:网站首页>本周小贴士#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++
边栏推荐
- 无法链接远程redis服务器(解决办法百分百)
- LeetCode1051(C#)
- How to choose the appropriate automated testing tools?
- 【TPM2.0原理及应用指南】 9、10、11章
- Skimage learning (3) -- adapt the gray filter to RGB images, separate colors by immunohistochemical staining, and filter the maximum value of the region
- 【饭谈】那些看似为公司着想,实际却很自私的故事 (一:造轮子)
- Nerf: the ultimate replacement for deepfake?
- LeetCode 1049. Weight of the last stone II daily question
- 电脑无法加域,ping域名显示为公网IP,这是什么问题?怎么解决?
- 鲲鹏开发者峰会2022 | 麒麟信安携手鲲鹏共筑计算产业新生态
猜你喜欢

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

Is AI more fair than people in the distribution of wealth? Research on multiplayer game from deepmind

How to choose the appropriate automated testing tools?

麒麟信安中标国网新一代调度项目!

如何在博客中添加Aplayer音乐播放器

redis主从、哨兵主备切换搭建一步一步图解实现

A tour of grpc:03 - proto serialization / deserialization

责任链模式 - Unity

Test case management tool recommendation

How to add aplayer music player in blog
随机推荐
跟奥巴马一起画方块(Lua)
LeetCode1051(C#)
LeetCode 1477. Find two subarrays with sum as the target value and no overlap
The computer cannot add a domain, and the Ping domain name is displayed as the public IP. What is the problem? How to solve it?
L1-025 正整数A+B(Lua)
centos7安装mysql笔记
Solidity 开发环境搭建
【TPM2.0原理及应用指南】 16、17、18章
L1-023 输出GPLT(Lua)
Matplotlib绘制三维图形
麒麟信安加入宁夏商用密码协会
DNS series (I): why does the updated DNS record not take effect?
无法链接远程redis服务器(解决办法百分百)
SIGGRAPH 2022最佳技术论文奖重磅出炉!北大陈宝权团队获荣誉提名
Blue Bridge Cup final XOR conversion 100 points
Reflections on "product managers must read: five classic innovative thinking models"
[fan Tan] after the arrival of Web3.0, where should testers go? (ten predictions and suggestions)
LeetCode 648(C#)
LeetCode 890(C#)
【网络攻防原理与技术】第3章:网络侦察技术