智能指针应该如何进行向下转型?

5

智能指针是否处理向下转换(down casting),如果不是,有什么安全的工作方法可以解决这个限制?

我想做的一个例子是拥有两个STL向量(例如),包含智能指针。第一个向量包含基类的智能指针,而第二个包含派生类的智能指针。智能指针计数引用,例如类似于Boost的shared_ptrs,但是手动实现的。我提供了一些示例代码:

vector<CBaseSmartPtr> vecBase;
vector<CDerivedSmartPtr> vecDer;
...
CBaseSmartPtr first = vecBase.front();
vecDer.push_back(CDerivedSmartPtr(dynamic_cast<CDerived*>(first.get()));

我认为这不是很安全,因为我觉得我最终会有两个智能指针管理同一个对象。在未来的某个时刻,这可能会导致其中一个释放对象,而另一个仍然持有对它的引用。

虽然我希望可以直接向下转型并保持相同的对象,但我认为这行不通,例如:

dynamic_cast<CDerivedSmartPtr>(first)

我是否应该将第二个容器更改为使用CBaseSmartPtr,并仅在使用时进行下转型?还有其他解决方案吗?


1
我认为这是一个有趣的问题,因为它涉及到智能指针实现的内部。但我也要指出,将向下转型构建到您的策略中意味着设计缺陷,在我看来。 - Peter Cardona
回答为什么我要自己写,是因为这是遗留代码,要将当前使用的代码全部替换成Boost将是一个相当大的改变。不过这已经列在我的“未来需要考虑的事项”清单上了。 - dlanod
“普通”的智能指针在共享所有权方面没有问题;事实上,常规的 Boost 智能指针甚至被命名为 shared_ptr。为什么对于您来说有两个智能指针共享所有权是个问题呢? - MSalters
@MSalters: first.get() 可能表明他们不知道他们应该分享。 - sbi
我不明白为什么“拆除并替换为boost.shared_ptr”比“拆除并替换为我自己制作的同样副本”更大、更激烈的变化。 - jalf
@jalf:事实上,使用boost,你只需要关注“剪切和替换”中的错误,而不必担心智能指针的问题。 - sbi
5个回答

5
智能指针可以处理向下转型,但这并不是自动的。而且,实现const正确性可能会有些复杂(我在面试问题中使用了我们的智能指针实现,其中涉及到一些模板技巧)。但是,许多智能指针的用户根本不会使用带有const限定类型的智能指针。
你需要正确处理的第一件事是计数器。由于您可能需要在smart_ptr和smart_ptr之间共享计数器,因此计数器类型不应取决于类型参数。总的来说,这并不是什么大问题。计数器只是一个size_t,可能包装在类中。(注意:还有其他智能指针设计,但问题强烈暗示使用计数器)
向基类的转换应该相当简单。因此,您的smart_ptr应该具有一个接受smart_ptr的构造函数。在此构造函数中,添加一行static_cast((U*)0);。这不会生成代码,但可以防止T不是U的基类(除了const修饰符)时进行实例化。
反过来则需要显式转换。您无法编程枚举T的所有基类,因此smart_ptr不能派生自smart_ptr、smart_ptr等。因此,dynamic_cast>将不起作用。您可以提供自己的smart_dynamic_cast(smart_ptr const& pU)。这最好实现为返回一个SPT的函数。在此函数中,您可以简单地执行return SPT(dynamic_cast(&*pU))。

其实,我认为你可以通过编程枚举基数。Andrej难道没有向我们展示过如何做到这一点吗? - sbi
拿到了这本书,但不在这里。虽然我不记得有这样的事情。为了消除任何混淆: "枚举基类" 的目标是确定一组类型 B1..Bn,给定一个类型 D,使得关系 is_base_and_derived<B,D> 对于所有类型 B1..Bn 都成立,而对于其他类型则不成立。 - MSalters
@MSalters:啊,我脑子抽了。我在想枚举类型并将它们作为基类...对不起。 - sbi

2
您需要的是指向类型的协变属性。也就是说,如果D是B,则您希望smartptr<D> isa smartptr<B>。我认为在C++中这并不优雅地得到了支持,但通常情况下,可以使用模板/重载的方法进行hack。 http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/pointer_cast.html提供了适用于常规和boost::smart_ptr的动态转换。如果您不想使用Boost,请从其实现中学习。

4
对于使用dynamic_pointer_cast,给予加1分;而声称C++没有协变返回类型则扣1分。参见此处链接:http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.8 - Michael Kristofik
@Kristo:我对这两点非常赞同,但我仍然认为这是非常好的建议。@王王:你为什么不删除错误的陈述呢?这会让其他人更容易给你赞同,否则这是一个不错的答案。 - sbi
你是对的 - 我没意识到虚方法允许协变返回类型!但确实如你所言,泛型类型,比如ptr<A> 和 vector<A>并不支持协变(尽管Java通过类型擦除实现了这一点)。 - Jonathan Graehl
@wang-wang:是的,仅使用核心语言无法实现从std::vector<Derived*>std::vector<Base*>的转换。我也认为这是C++的不足之处。 - sbi

1

请在Boost邮件列表之一中此处关注该线程。它展示了在boost::shared_ptr的情况下如何实现智能指针向下转换。祝您好运!


看起来真的很可怕。他正在将 shared_ptr<T>* 转换为 shared_ptr<U>*。另请参阅 Rainer Deyke 的回答。 - MSalters

0

普通的智能指针,如std::auto_ptr,在STL容器中使用不安全,因为当STL将智能指针实例分配给彼此时,所有权会移动,因为它在内部复制数据。您需要使用类似boost::shared_ptr的东西,它在内部实现引用计数,以确保一个对象保持活动状态,无论有多少智能指针实例引用它。如果您正在编写自己的智能指针类型,则需要实现类似的引用计数。


谢谢,我会在问题中添加一条注释,明确表示我打算使用引用计数智能指针。 - dlanod

0

我在微软页面上找到了这个:

    std::shared_ptr<base> sp0(new derived); 
    std::shared_ptr<derived> sp1 = 
    std::dynamic_pointer_cast<derived>(sp0); 

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