为什么unique_ptr<T>(T*)需要显式声明?

30
以下函数无法编译:
std::unique_ptr<int> foo()
{
    int* answer = new int(42);
    return answer;
}

std::unique_ptr<int> bar()
{
    return new int(42);
}

我觉得这有点不方便。为什么要让std::unique_ptr<T>(T*)成为显式的呢?


4
出于相同的原因,shared_ptr 是显式的(explicit)。 - jxh
你真的可以在返回类型中使用 unique_ptr 吗?这对我来说没有意义,因为整个重点是你不能复制它。 - cha0site
7
这是一个临时文件,所以它是被移动而不是复制。 - Mooing Duck
1
@MooingDuck:即使它不是临时的,局部对象在返回语句中也会被隐式地视为xvalues。 - ildjarn
2个回答

33

你不希望一个托管指针隐式地获取裸指针的所有权,因为这可能导致未定义的行为。考虑一个函数void f( int * );和一个调用int * p = new int(5); f(p); delete p;。现在想象一下,有人将f重构为接受托管指针(任何类型),并允许隐式转换:void f( std::unique_ptr<int> p );如果允许隐式转换,则您的代码将编译,但会导致未定义的行为。

同样地,考虑指针可能甚至没有动态分配:int x = 5; f( &x );...

获取所有权是一个非常重要的操作,最好是显式的:程序员(而不是编译器)知道资源是否应该通过智能指针进行管理。


4
+1,特别是对于“所有权的获取是一个非常重要的操作,最好明确地表达出来。” - Macke
“所有权的获取是一个非常重要的操作,最好明确地进行。” 很好的观点。 - curiousguy

20

简短回答:

显式构造函数使编写危险代码更加困难。换句话说,隐式构造函数使你更容易编写危险代码。

详细回答:

如果构造函数是隐式的,那么你可以更轻松地编写这样的代码:

void f(std::unique_ptr<int> param)
{
     //code

} //param will be destructed here, i.e when it goes out of scope
  //the pointer which it manages will be destructed as well. 

现在看看危险的部分:

int *ptr = new int;

f(ptr); 
//note that calling f is allowed if it is allowed:
//std::unique_ptr<int> test = new int;
//it is as if ptr is assigned to the parameter:
//std::unique_ptr<int> test = ptr;

//DANGER
*ptr = 10; //undefined behavior because ptr has been deleted by the unique_ptr!
请阅读注释。它解释了上面代码片段的每个部分。
使用原始指针调用f()时,程序员可能没有意识到f()的参数类型是std::unique_ptr,这将获取指针的所有权,并在其超出范围时将其delete。与此同时,程序员可能会使用它,并delete它,甚至没有意识到它已经被删除了!这一切都是由于从原始指针到std::unique_ptr隐式转换引起的。
请注意,std::shared_ptr具有显式构造函数,原因完全相同。

这两个选项哪一个风险更低:void f(int *param) { std::unique_ptr<int> smart(param); } 还是 void f(int *param) { delete param; } - curiousguy
我们必须接受这一点,而显式构造函数并不能像我们希望的那样保护我们:static_cast<array>(1) 仍然是可能的。在 C++ 中,我们没有非转换构造函数,因为 array(1) 是一种转换(一种到类型 array 的强制转换)。 - curiousguy
第四,正如你最终承认的那样:“显然,隐式转换比显式转换更容易被意外调用。”恰恰是我在我的答案中所说的。谢谢。;-) - Nawaz
@curiousguy:“不,我从来没有写过那个。” 但是你确实写过“但是C++的设计意味着一个参数构造函数实际上是一个显式转换”(尽管这并不完全正确;只有在构造函数声明时使用关键字explicit才是正确的,否则它是一个隐式转换函数)。如果to_string()不是一个转换函数,那么它是什么?如果它不将参数转换string,那么它是做什么的? - Nawaz
同时,使用显式构造函数可以轻松编写 void f(std::unique_ptr<int> param){}。不同之处在于您如何调用该函数。 - 463035818_is_not_a_number
显示剩余20条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接