是否支持从 unique_ptr 到原始指针的隐式转换?

Is implicit conversion from unique_ptr to raw pointer supported?

我正在阅读 Effective C++ 第三版。在第 70 页,作者说:

Like virtually all smart pointer classes, tr1::shared_ptr and auto_ptr also overload the pointer dereferencing operators (operator-> and operator*), and this allows implicit conversion to the underlying raw pointers (...)

然后他展示了一个带有 shared_ptr(当时是 tr1 的一部分)的示例,该示例具有基于名为 Investment 的 class 的隐式转换:

shared_ptr<Investment> pi1();
bool taxable1 = !(pi1->isTaxFree());
                    ^implicit conversion

shared_ptr<Investment> pi2();
bool taxable2 = !((*pi2).isTaxFree());
                    ^implicit conversion

好吧,从那以后我用 unique_ptr 写了一些测试用例,它们都有效。

我还发现了 unique_ptr supporting arrays and shared_ptr also going to(见注释)。但是,在我的测试中,隐式转换似乎不适用于数组周围的智能指针。

示例: 我希望这是有效的...

unique_ptr<int[]> test(new int[1]);

(*test)[0] = 5;

但根据我的编译器 (Visual C++ 2015 Update 3),它不是。

然后,通过一些研究,我发现了一些证据表明根本不支持隐式转换......例如这个:https://herbsutter.com/2012/06/21/reader-qa-why-dont-modern-smart-pointers-implicitly-convert-to.

在这一点上我有疑问。 支持(标准)还是不支持?


注意:本书在这个主题上可能有点过时,因为作者还在第 65 页上说 "there is nothing like auto_ptr or tr1::shared_ptr for dinamically allocated arrays, not even in TR1".

嗯,事情是这样的。没有到底层指针的隐式转换,您必须调用特定的 get 成员函数(这是标准库中的主题,想想 std::string::c_str)。

但这是一件好事!隐式转换指针会破坏 unique_ptr 的保证。考虑以下因素:

std::unique_ptr<int> p1(new int);
std::unique_ptr<int> p2(p1);

在上面的代码中,编译器可以尝试将p1的指针传递给p2! (它不会,因为这个调用无论如何都会模棱两可,但假设它不是)。他们都会在上面调用 delete

但我们仍然想像使用原始智能指针一样使用智能指针。因此所有运算符都超载了。


现在让我们考虑一下您的代码:

(*test)[0] = 5;

它调用 unique_ptr::operator* 生成 int&1。然后你尝试在它上面使用下标运算符。那是你的错误。
如果你有一个 std::unique_ptr<int[]> 而不是只使用句柄提供的 operator[] 重载:

test[0] = 5;

1 正如 David Scarlett 指出的那样,它甚至不应该编译。数组版本不应该有这个运算符。

正如 StoryTeller 指出的那样,隐式转换会毁掉节目,但我想提出另一种思考方式:

unique_ptrshared_ptr 这样的智能指针试图隐藏底层的原始指针,因为它们试图在其上维护某种所有权语义。如果您要自由获取该指针并将其四处传递,则很容易违反这些语义。他们仍然提供了一种访问它的方法 (get),因为即使他们想阻止你也无法完全阻止你(毕竟你可以跟随智能指针并获得指针的地址)。但他们仍然想设置一道屏障以确保您不会意外访问它。

虽然一切都没有丢失!您仍然可以通过定义一种语义非常弱的新型智能指针来获得语法上的便利,这样它就可以安全地从大多数其他智能指针隐式构造。考虑:

// ipiwdostbtetci_ptr stands for : 
// I promise I wont delete or store this beyond the expression that created it ptr
template<class T>
struct ipiwdostbtetci_ptr {
    T * _ptr;
    T & operator*() {return *_ptr;}
    T * operator->(){return _ptr;}
    ipiwdostbtetci_ptr(T * raw): _ptr{raw} {}
    ipiwdostbtetci_ptr(const std::unique_ptr<T> & unq): _ptr{unq.get()} {}
    ipiwdostbtetci_ptr(const std::shared_ptr<T> & shr): _ptr{shr.get()} {}
};

那么,这个名字讽刺的智能指针有什么意义呢?它只是一种口头上的指针,用户永远不会在创建它的表达式之外保留它或它的副本,并且用户也永远不会尝试删除它。有了这些用户遵循的约束(没有编译器检查它),隐式转换许多智能指针以及任何原始指针是完全安全的。

现在您可以实现期望 ipiwdostbtetci_ptr 的函数(假设它们会遵守语义),并方便地调用它们:

void f(ipiwdostbtetci_ptr<MyClass>);
...
std::unique_ptr<MyClass> p = ...
f(p);