将shared_ptr<Base>向下转型为shared_ptr<Derived>?

137

更新:这个示例中的shared_ptr类似于Boost中的那个,但它不支持shared_polymorphic_downcast(或dynamic_pointer_cast或static_pointer_cast)!

我正在尝试初始化一个指向派生类的共享指针,而不会丢失引用计数:

struct Base { };
struct Derived : public Base { };
shared_ptr<Base> base(new Base());
shared_ptr<Derived> derived;

// error: invalid conversion from 'Base* const' to 'Derived*'
derived = base;  

到目前为止都还不错。我没有想到C++会隐式转换Base*指针为Derived*指针。尽管如此,我仍然需要代码中表达的功能(即,在向下转换基类指针时保持引用计数)。我的第一个想法是在Base类中提供一个类型转换运算符,以便可以进行隐式转换到Derived(对于学究们:我会检查向下转换是否有效,不用担心):

struct Base {
  operator Derived* ();
}
// ...
Base::operator Derived* () {
  return down_cast<Derived*>(this);
}

没什么用,编译器好像完全忽略了我的类型转换运算符。你们有什么想法可以让 shared_ptr 赋值成功吗?附加问题: Base* const 是什么类型?我知道const Base*是什么意思,但Base* const中的const指代什么?


为什么需要一个shared_ptr<Derived>,而不是shared_ptr<Base>? - Bill
8
我希望你可以在Derived中访问Base中没有的功能,而不必克隆该对象(我想要一个被两个共享指针引用的单个对象)。顺便问一下,为什么转换运算符无法起作用? - Lajos Nagy
4个回答

164

你可以使用dynamic_pointer_cast,这是由std::shared_ptr支持的。

std::shared_ptr<Base> base (new Derived());
std::shared_ptr<Derived> derived =
               std::dynamic_pointer_cast<Derived> (base);

文档:https://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast

另外,我不建议在基类中使用转换运算符。这样的隐式转换可能会成为错误和 bug 的源头。

-更新:如果类型不是多态的,则可以使用std::static_pointer_cast


5
我一开始没看懂他没有使用 std::shared_ptr 这句话的意思。但从第一个回答的评论中我推断出他没有使用 boost,所以他可能在使用 std::shared_ptr - Masood Khaari
好的。抱歉。他最好澄清一下他正在使用自定义实现。 - Masood Khaari

51

我假设你正在使用boost::shared_ptr... 我认为你需要使用dynamic_pointer_cast或者shared_polymorphic_downcast。但是,这需要多态类型。

Base* const 是什么类型?const Base* 我理解,但是Base* const 指的是什么?在这种情况下,const 指的是什么?

  • const Base * 是一个可变指向常量Base的指针。
  • Base const * 是一个可变指向常量Base的指针。
  • Base * const 是一个指向可变Base的常量指针。
  • Base const * const 是一个指向常量Base的常量指针。

以下是一个简单的示例:

struct Base { virtual ~Base() { } };   // dynamic casts require polymorphic types
struct Derived : public Base { };

boost::shared_ptr<Base> base(new Base());
boost::shared_ptr<Derived> derived;
derived = boost::static_pointer_cast<Derived>(base);
derived = boost::dynamic_pointer_cast<Derived>(base);
derived = boost::shared_polymorphic_downcast<Derived>(base);

我不确定你的示例是否有意创建了一个基类实例并进行了强制转换,但它很好地说明了差异。

static_pointer_cast会"直接执行"。这可能导致未定义行为(Derived*指向为Base分配和初始化的内存),并且可能会导致崩溃或更糟。 base的引用计数将被增加。

dynamic_pointer_cast将导致空指针。 base的引用计数将不变。

shared_polymorphic_downcast将具有与静态转换相同的结果,但将触发断言,而不是似乎成功并导致未定义行为。 base的引用计数将被增加。

参见(失效链接):

有时很难决定使用static_cast还是dynamic_cast,您希望两全其美。众所周知,动态转换具有运行时开销,但较安全,而静态转换根本没有开销,但可能会在失败时静默失败。如果您可以在调试版本中使用shared_dynamic_cast,并在发布版本中使用shared_static_cast,那将非常好。现在已经有这样的事情了,它被称为shared_polymorphic_downcast


很遗憾,您的解决方案依赖于 Boost 功能,而该功能被故意从我们正在使用的特定 shared_ptr 实现中排除(不要问为什么)。至于 const 的解释,现在更加合理了。 - Lajos Nagy
3
除了实现其他shared_ptr构造函数(使用static_cast_tagdynamic_cast_tag),你没有什么可以做的。 在shared_ptr之外进行任何操作都将无法管理引用计数。--在“完美”的面向对象设计中,您总是可以使用基础类型,而不需要知道也不需要关心派生类型,因为所有功能都通过基类接口公开。也许你需要重新考虑为什么需要首先进行向下转换。 - Tim Sylvester
1
@Tim Sylvester:但是,C++并不是一个“完美”的面向对象语言! :-) 向下转型在一个不完美的面向对象语言中有其存在的意义。 - Steve Folly

5

如果有人使用boost::shared_ptr,请看这里...

以下是如何将Derived类型向下转换为其基类Base的Boost shared_ptr。

boost::shared_ptr<Base> bS;
bS.reset(new Derived());

boost::shared_ptr<Derived> dS = boost::dynamic_pointer_cast<Derived,Base>(bS);
std::cout << "DerivedSPtr  is: " << std::boolalpha << (dS.get() != 0) << std::endl;

确保“Base”类/结构至少有一个虚函数。虚析构函数也可以。


-1
本周我遇到了同样的问题,但我的解决方案不涉及现代 C++(≥20)中仅可用的新类型转换。我只使用 reinterpret_cast 和在转换之间创建的共享指针的 const &,如下面的代码所示:
std::shared_ptr<Base> basePtr = createBasePtr();
std::shared_ptr<Derived> derivedPtr = reinterpret_cast<const std::shared_ptr<Derived>&>(basePtr);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接