如何在创建unique_ptr时防范构造函数的隐式转换问题?
这个问题我太有共鸣了!之前重构代码修改构造函数参数时,就踩过一模一样的坑——直接栈上初始化对象的时候,编译器会立刻报错提醒参数不匹配,可一用std::make_unique,居然悄咪咪通过编译了,等运行时逻辑出问题才发现是隐式转换搞的鬼,真的挺闹心的。
先把你遇到的场景再理清楚,方便大家理解:
问题复现场景
原来的类有两个带pad参数的构造函数:
class Example { public: Example(int m, int n, double pad) {} Example(double size, double pad) {} }; int main() { Example example { 1.0, 1.0 }; // 参数数量不匹配,编译报错 auto ptr = std::make_unique<Example>(1.0, 1.0); // 参数数量不匹配,编译报错 }
当你移除两个构造函数的pad参数后,奇怪的问题出现了:
class Example { public: Example(int m, int n) {} Example(double size) {} }; int main() { Example example { 1.0, 1.0 }; // 编译报错!因为1.0是double,和int参数不匹配,这反而帮我们及时发现问题 auto ptr = std::make_unique<Example>(1.0, 1.0); // 居然还能编译!实际是把两个double隐式转换成int,匹配了第一个构造函数,参数含义完全变了却没有任何提醒 }
问题根源
这是因为std::make_unique的参数是通过完美转发传递给构造函数的,C++在这里会允许隐式类型转换来匹配构造函数;而你用大括号{}直接初始化对象时,触发的是列表初始化,对类型匹配的检查更严格,不允许这种隐式转换,所以会直接报错。
解决办法
针对这个问题,有两个实用的解决思路:
方案一:改用
new Example { ... }配合unique_ptr构造
就像你想到的那样,直接用列表初始化创建对象,再传给unique_ptr的构造函数:auto ptr = std::unique_ptr<Example>(new Example { 1.0, 1.0 });这种方式和你直接创建局部对象的初始化逻辑完全一致,编译器会严格检查参数类型和数量,一旦不匹配就会报错,不会发生悄咪咪的隐式转换,能帮你及时发现问题。
方案二:给构造函数加上
explicit关键字
如果你想从根源上禁止构造函数的隐式转换,不管是哪种创建对象的方式,都可以给构造函数加上explicit修饰:class Example { public: explicit Example(int m, int n) {} explicit Example(double size) {} };这样一来,任何需要隐式转换才能匹配构造函数的代码都会直接编译报错——不管是
make_unique(1.0, 1.0)还是其他场景,从根源上杜绝这种“悄咪咪改了参数含义”的问题。
总结
如果只是想在创建unique_ptr时避免这个坑,方案一简单直接;如果希望整个代码里都禁止这个类的构造函数隐式转换,方案二更彻底,能避免其他地方出现类似的问题。
内容来源于stack exchange

