【问题标题】:How are smart pointers of a derived class implicitly convertible to the base class?派生类的智能指针如何隐式转换为基类?
【发布时间】:2020-08-07 13:40:33
【问题描述】:

来自cppreference

如果T 是某个基类B 的派生类,那么std::unique_ptr<T> 是 隐式转换为std::unique_ptr<B>

显然,多态性必须像处理原始指针一样工作。我的问题是,如果智能指针通常不能转换为我们可以看到的指针here,那么智能指针使用什么机制来实现运行时多态性?我的想法是在构造函数或std::make_unique<>()/std::make_shared<>() 对象中的内部指针用于此转换。但如果这些隐式转换在其他任何地方都不允许,为什么我们在构造智能指针时不必调用 get() 呢?

作为一个非常简单的例子,我想出了以下测试:

#include <iostream>
#include <memory>

class Base
{
public:
    virtual ~Base() = default;

    virtual void foo() const { std::cout << "Base foo() called." << std::endl; }
};

class Derived : public Base
{
public:
    virtual void foo() const override { std::cout << "Derived foo() called." << std::endl; }
};

void bar(Base* pBase) 
{
    std::cout << "bar() called." << std::endl;
    pBase->foo();
}

int main()
{
    std::unique_ptr<Base> pObject { std::make_unique<Derived>() };      // Implicit conversion here, why no call to get()?

    // bar(pObject);                                                    // Can't be converted, so we have to call get()
    bar(pObject.get());
}

【问题讨论】:

  • 隐式转换 ...正确。 为什么不调用 get()? ...因为这行不通,你最终会得到两个智能指针来管理同一个对象的生命周期。
  • @Eljay 如果这两个是shared_ptr&lt;&gt; 而不是unique_ptr&lt;&gt; 的情况也会如此吗?
  • 更抽象地思考。您首先声明某个类可以隐式转换为另一个类。为什么你期望这个隐式转换首先需要隐式转换到T*?为什么要向最终用户公开隐式转换的内部机制?
  • 是的,如果你从智能指针中取出原始指针,而不让智能指针放弃原始指针的所有权,然后将它交给另一个智能指针来管理,两个智能指针都会不知不觉地管理了对象的生命周期。在一个智能指针销毁对象后,另一个智能指针对现在删除的对象的任何使用(包括删除它)都将导致未定义的行为。这是一件坏事™。
  • @JaMiT 我想我被绊倒了,因为如果没有到 T* 的转换,更多的类不能被没有指针的派生类型实例化吗?也许这实际上是允许的,我只是让它变得比它需要的更复杂

标签: c++ smart-pointers


【解决方案1】:

我的问题是,如果智能指针通常不能像我们在这里看到的那样转换为指针,那么智能指针使用什么机制来实现运行时多态性?

智能指针被明确设计为使这种转换成为可能。正如您在std::unique_ptr 构造函数documentation 中看到的那样:

template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; (6)

这个重载就是为此目的而创建的。

这个构造函数只参与重载决议,如果所有 以下是正确的:

a) unique_ptr::pointer 是隐式的 可转换为指针

b) U 不是数组类型

c) 删除器是 引用类型并且 E 与 D 的类型相同,或者 Deleter 不是 引用类型和 E 可以隐式转换为 D

重点是我的。所以指向派生类的指针是隐式的 convertible to base 这个构造函数重载使得这种转换成为可能。

【讨论】:

  • 啊,所以是内部存储的指针是可转换的,而不是智能指针本身?如果是这样,是在运行时检查转换还是编译器能够推断出这两种类型在编译时是可转换的?
  • @crdrisko 只有在编译时可以确定它才能隐式转换。你也不能被BasePtr* p; DerivedPtr* d = p打倒。
  • @crdrisko 模板实例化发生在编译时,因此所有检查也是如此。您的陈述也不太正确,如果内部指针是可隐式转换的,那么这样的智能指针就是,因为单参数构造函数提供类型转换。
【解决方案2】:

.get() 返回的指针不会将所有权转移给调用者(即.release())。

如果您使用来自.get() 的指针来构造另一个智能指针,您将获得双释放。

所以这是一个错误:

std::unique_ptr<Base> pObject { std::make_unique<Derived>().get() };

这行得通

std::unique_ptr<Base> pObject { std::make_unique<Derived>().release() };

对于shared_ptr,从.get().release() 构造是错误的,因为您可能有其他实例具有相同的共享状态,而您没有转移。因此,您最终可能会得到两个指向同一个指针但共享状态不同的智能指针。

【讨论】:

  • 您能否详细说明 shared_ptr 如何处理 get() 或 release()?与内部指针本身的问题相反,这是否与两者的计数不同有关?
  • @crdrisko 是的,使用计数是共享状态的一部分
猜你喜欢
  • 1970-01-01
  • 2013-09-23
  • 2016-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-16
  • 2021-02-09
  • 1970-01-01
相关资源
最近更新 更多