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

Is implicit conversion from unique_ptr to raw pointer supported?

本文关键字:指针 转换 原始 支持 unique ptr 是否      更新时间:2023-10-16

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

像几乎所有智能指针类一样,tr1::shared_ptrauto_ptr也重载指针解引用操作符(operator->operator*),这允许隐式转换为底层原始指针(…)

然后他展示了一个shared_ptr(当时是tr1的一部分)的例子,基于一个名为Investment的类进行隐式转换:

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

从那以后,我用unique_ptr写了几个测试用例,它们都很好。

我还发现unique_ptr支持数组,shared_ptr也将(见注释)。然而,在我的测试中,隐式转换似乎对数组周围的智能指针不起作用。

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

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页上说"对于动态分配的数组,没有像auto_ptrtr1::shared_ptr这样的东西,甚至在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);