【问题标题】:Alternatives of static_pointer_cast for unique_ptrunique_ptr 的 static_pointer_cast 的替代方案
【发布时间】:2016-03-20 22:28:28
【问题描述】:

我了解将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_ptr s 因为我上面提到的。

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

【问题讨论】:

  • static_pointer_cast 只为std::shared_ptr&lt;T&gt; 类型的参数定义 - 它根本不能用于unique_ptr
  • @M.M 是的,我知道,我在问题中写的几乎相同,说不存在 unique_ptr 的等价物。
  • 为什么类型擦除会导致向下转换?如果您需要将基类转换为派生类型,除了极少数情况外,这通常是一个应该以不同方式解决的设计问题。
  • @Jens Imagine struct B { virtual void f() = 0; }; template&lt;class T&gt; struct D: public B { void f() override { /* do something with D */ } };,你做过类似的事情吗?通过包装器和static_pointer_cast 的良好组合,您可以创建很多有趣的东西。
  • @javaLover 好吧,我想说其他答案有一个更直接和更干净的方法,但是......它有效......就是这样。 :-)

标签: c++ c++11 casting shared-ptr unique-ptr


【解决方案1】:

#原始指针

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

像这样:

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 无效

#来自原始指针的引用 为了回答的完整性,这个解决方案实际上是由 cmets 中的 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

【讨论】:

  • 为什么不颠倒这些模板参数的顺序,让FROM被推导出来,就像std::static_pointer_cast一样?
  • @Anedar 是的,显然我不想处理原始指针,但无论如何你给了我一个很好的提示......static_unique_pointer_cast 的实现很有趣,但不幸的是不适合存储这些指针的想法(在问题中表达),因为一旦它们被无效,它们必须在当前使用后重新初始化以供将来使用,因此会导致大量围绕容器工作。
  • @skypjack 出现的问题是:所指向对象的所有者是谁(即负责删除的对象)?一次只有一个指针? -> 使用 unique_ptr,一次可能只有一个。多个(可能是不同的类别)? -> 使用共享指针。对于不负责删除的指针,使用原始指针。如果您决定拥有一个所有者,那么它们可能是获取不同类的指针指向同一个对象同时保持原始对象所有权的唯一方法。
  • 好吧,不是那么容易,它是一组指向类型擦除类的指针,一旦需要就会向前转换,所以所有者是一个,但我仍然会遇到问题,就好像它们是两个一样。无论如何,这是一个使用get 获取原始指针的问题,对其进行强制转换并取消引用它,以便返回一个可以保证其生命周期的引用。否则我也可以试试shared_ptrs,当然...... :-)
  • 好的,这基本上是使用原始指针,但有一个额外的取消引用,以使您不必事后处理指针逻辑。听起来是一个很好的解决方案,因为您说您可以保证引用的生命周期不超过 unique_ptr 的生命周期。
【解决方案2】:

我了解将 static_pointer_cast 与 unique_ptr 一起使用会导致所包含数据的共享所有权。

只有当你定义不好时。显而易见的解决方案是让它转移所有权,这样源对象就变成空的了。

如果您不想转让所有权,则只需使用原始指针即可。

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

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

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

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

shared_ptr 的情况并非如此,“显而易见”的解决方案并没有做正确的事情:

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

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

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

但是,在 C++11 标准化过程中,shared_ptr 通过添加“别名构造函数”得到了增强,它允许您简单安全地进行转换:

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

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

【讨论】:

  • 嗯,它不存在,因为自己编写很简单 也可以应用于 STL 的其他一些部分(存在),但我得到了你回答的重点。 +1
  • 你错过了句子的结尾,“如果你需要的话”。如果事情很难/不可能让用户正确编写(最初对于 shared_ptr 强制转换是正确的),或者如果它们很容易但每个人都在不断地重新实现它,那么它们是值得加入标准的。 unique_ptr 转换也不是真的,因为它很容易,但并不经常需要。
  • 我同意,老实说,我猜我正面临一个 XY 问题,但这个问题对我来说仍然很有趣,知道如果他们是我,其他人会怎么做。
  • std::exchange 很容易写,没人用。它是怎么进来的? :-)
  • @HowardHinnant 我认为std::exchangea duck 一样好用。
【解决方案3】:

我想在Anedar 的上一个答案中添加一些内容,它调用给定std::unique_ptr&lt; U &gt;release() 成员方法。如果还想实现dynamic_pointer_cast(除了static_pointer_cast)将std::unique_ptr&lt; U &gt; 转换为std::unique_ptr&lt; T &gt;,则必须确保正确释放由唯一指针保护的资源,以防dynamic_cast 失败(即返回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()));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-03
    • 2015-09-25
    • 2019-12-16
    • 2011-06-20
    • 2015-11-03
    • 2014-04-03
    • 2011-12-13
    • 2014-02-28
    相关资源
    最近更新 更多