static_pointer_cast 的替代 unique_ptr

Alternatives of static_pointer_cast for unique_ptr

我知道将 static_pointer_castunique_ptr 一起使用会导致所包含数据的共享所有权。
换句话说,我想做的是:

unique_ptr<Base> foo = fooFactory();
// do something for a while
unique_ptr<Derived> bar = static_unique_pointer_cast<Derived>(foo);

无论如何这样做会导致两个 unique_ptr 永远不会同时存在,所以它是被禁止的。
是的,这很有道理,绝对,这就是为什么不存在像static_unique_pointer_cast这样的东西的原因。

到目前为止,如果我想存储指向那些基 类 的指针,但我还需要将它们转换为一些派生的 类(例如,想象一个涉及类型的场景擦除),我使用 shared_ptrs 因为我上面提到的。

无论如何,我在猜测是否有 shared_ptrs 的替代方案来解决这样的问题,或者它们是否真的是那种情况下的最佳解决方案。

#原始指针

你的问题的解决方案是获取原始(非拥有)指针并将其转换 - 然后让原始指针超出范围并让剩余的 unique_ptr<Base> 控制拥有的生命周期对象。

像这样:

unique_ptr<Base> foo = fooFactory();

{
    Base* tempBase = foo.get();
    Derived* tempDerived = static_cast<Derived*>(tempBase);
} // tempBase and tempDerived go out of scope here, but foo remains -> no need to delete

#Unique_pointer_cast 另一种选择是使用 unique_ptrrelease() 函数将其包装到另一个 unique_ptr.

像这样:

template<typename TO, typename FROM>
unique_ptr<TO> static_unique_pointer_cast (unique_ptr<FROM>&& old){
    return unique_ptr<TO>{static_cast<TO*>(old.release())};
    // conversion: unique_ptr<FROM>->FROM*->TO*->unique_ptr<TO>
}

unique_ptr<Base> foo = fooFactory();

unique_ptr<Derived> foo2 = static_unique_pointer_cast<Derived>(std::move(foo));

记住这会使旧指针失效foo

#来自原始指针的引用 为了回答的完整性,这个解决方案实际上是 OP 在评论中提出的对原始指针的一个小修改。

类似于使用原始指针,您可以强制转换原始指针,然后通过取消引用从中创建一个引用。在这种情况下,重要的是要保证创建的引用的生命周期不超过 unique_ptr.

的生命周期

样本:

unique_ptr<Base> foo = fooFactory();
Derived& bar = *(static_cast<Derived*>(foo.get()));
// do not use bar after foo goes out of scope

I understand that using static_pointer_cast with unique_ptr would lead to a shared ownership of the contained data.

除非你定义不好。显而易见的解决方案是转移所有权,以便源对象最终为空。

如果您不想转移所有权,那么只需使用原始指针。

或者,如果您想要两个所有者,则使用 shared_ptr

您的问题似乎部分与实际的转换操作有关,部分只是缺少明确的指针所有权策略。如果你需要多个所有者,无论他们都使用相同的类型,还是一个被转换为不同的类型,那么你不应该使用 unique_ptr.

Anyway doing that results with two unique_ptr that should never exist at the same time, so it is simply forbidden.
Right, it makes sense, absolutely, that's why there doesn't exist anything like static_unique_pointer_cast indeed.

不,这不是它不存在的原因。它不存在,因为如果您需要它,您自己编写它是微不足道的(只要您赋予它唯一所有权的理智语义)。只需用 release() 将指针取出,然后将其放入另一个 unique_ptr。简单安全。

shared_ptr 的情况并非如此,其中 "obvious" 解决方案没有做正确的事情:

shared_ptr<Derived> p2(static_cast<Derived*>(p1.get());

这将创建两个不同的 shared_ptr 对象,它们拥有相同的指针,但不共享所有权(即它们都会尝试删除它,从而导致未定义的行为)。

shared_ptr 首次标准化时,没有安全的方法可以做到这一点,因此定义了 static_pointer_cast 和相关的转换函数。他们需要访问 shared_ptr 簿记信息的实施细节才能工作。

然而,在 C++11 标准化过程中,shared_ptr 通过添加 "aliasing constructor" 得到了增强,它允许您简单而安全地进行转换:

shared_ptr<Derived> p2(p1, static_cast<Derived*>(p1.get());

如果此功能一直是 shared_ptr 的一部分,那么 static_pointer_cast 可能永远不会被定义。

我想对 的先前答案添加一些内容,它调用给定 std::unique_ptr< U >release() 成员方法。如果要实现 dynamic_pointer_cast 以及(除了 static_pointer_cast)以将 std::unique_ptr< U > 转换为 std::unique_ptr< T >,则必须确保释放由唯一指针保护的资源以防 dynamic_cast 失败(即 returns a nullptr)。否则会发生内存泄漏。

代码:

#include <iostream>
#include <memory>

template< typename T, typename U >
inline std::unique_ptr< T > dynamic_pointer_cast(std::unique_ptr< U > &&ptr) {
    U * const stored_ptr = ptr.release();
    T * const converted_stored_ptr = dynamic_cast< T * >(stored_ptr);
    if (converted_stored_ptr) {
        std::cout << "Cast did succeeded\n";
        return std::unique_ptr< T >(converted_stored_ptr);
    }
    else {
        std::cout << "Cast did not succeeded\n";
        ptr.reset(stored_ptr);
        return std::unique_ptr< T >();
    }
}

struct A { 
    virtual ~A() = default;
};
struct B : A {
    virtual ~B() { 
        std::cout << "B::~B\n"; 
    }
};
struct C : A {
    virtual ~C() { 
        std::cout << "C::~C\n"; 
    }
};
struct D { 
    virtual ~D() { 
        std::cout << "D::~D\n"; 
    }
};

int main() {

  std::unique_ptr< A > b(new B);
  std::unique_ptr< A > c(new C);
  std::unique_ptr< D > d(new D);

  std::unique_ptr< B > b1 = dynamic_pointer_cast< B, A >(std::move(b));
  std::unique_ptr< B > b2 = dynamic_pointer_cast< B, A >(std::move(c));
  std::unique_ptr< B > b3 = dynamic_pointer_cast< B, D >(std::move(d));
}

输出(可能排序):

Cast did succeeded
Cast did not succeeded
Cast did not succeeded
B::~B
D::~D
C::~C

CD 的析构函数不会被调用,如果使用:

template< typename T, typename U >
inline std::unique_ptr< T > dynamic_pointer_cast(std::unique_ptr< U > &&ptr) {
    return std::unique_ptr< T >(dynamic_cast< T * >(ptr.release()));
}