【问题标题】:SmartPointer : cast between base and derived classesSmartPointer:基类和派生类之间的转换
【发布时间】:2011-08-03 18:30:52
【问题描述】:

假设你有这样的功能:

SmartPtr<A> doSomething(SmartPtr<A> a);

还有这样的课程:

class A { }
class B : public A { }

现在我这样做了:

SmartPtr<A> foo = new B();
doSomething(foo);

现在,我想从doSomething 取回一个SmartPtr&lt;B&gt; 对象。

SmartPtr<B> b = doSomething(foo); 

有可能吗?我必须做什么样的演员?
现在,我刚刚发现了一些我认为丑陋的东西:

B* b = (B*)doSomething().get()

重要提示:我无权访问SmartPtrdoSomething() 代码。

【问题讨论】:

  • 你知道 SmartPtr 是从哪里来的吗?有什么文件吗?
  • 任何值得称道的智能指针库都将提供专门的强制转换函数来执行此操作。例如在 boost with shared_ptr 它的 boost::shared_ptr&lt;A&gt; b = boost::dynamic_ptr_cast&lt;B&gt;(foo) 寻找类似的东西。
  • 嗯,这是一个非常基本的 SmartPtr 库,没有任何东西允许动态转换。

标签: c++ inheritance casting smart-pointers


【解决方案1】:

你可以这样做:

B *b = dynamic_cast< B* >( doSomething.get() );

但你必须检查 b 是否为 NULL。

【讨论】:

  • 建议在类层次结构中向上转换时始终使用dynamic_cast,并且每次都检查NULL。除非您可以使用静态转换,这意味着编译器发现您实际上可以进行转换,因为指针指向 B* 对象。请记住,dynamic_cast 需要 RTTI,这会增加输出文件的大小。
  • ...但 RTTI 的手动替代方案也会增加文件大小。通常,功能会增加文件大小。标准 RTTI 的一大优势在于它经过了广泛的测试和优化。
  • 感谢您的回答。但请记住,我想要一个 SmartPtr 而不是 B*。
  • 实际上我使用的是外部库,无法修改 SmartPtr 实现。但是,你终于回答了我的问题:我不能从 SmartPtr 转换为 SmartPtr,即使 B 是从 A 派生的,如果 SmartPtr 没有提供某种方法来进行转换。我说的对吗?
  • @Antoine 这取决于 SmartPtr,但很可能你是对的,因为 SmartPtr 应该释放对象,除非它做一些聪明的事情。
【解决方案2】:

您可以定义自己的 SmartPtrCast 模板函数,该函数执行以下操作:

template <typename DestT, typename SrcT>
inline SmartPtr<DestT> SmartPtrCast(const SmartPtr<SrcT> &src)
{
    return SmartPtr<DestT>(static_cast<DestT*>(src.get()));
}

那么,你所要做的就是优雅地从 A 转换到 B:

SmartPtr<B> b = SmartPtrCast<B>(doSomething(foo));

注意事项: 这仅在doSomething() 返回的智能指针在其他地方被引用时才有效,并且在超出范围时不会被销毁。从你的例子来看,情况确实如此,但它仍然不那么优雅,应该注意这两个指针不会共享它们的引用计数(所以如果其中一个被破坏,第二个将丢失其数据)。

更好的解决方案是分离其中一个指针(如果 SmartPtr 具有分离方法)。一个更好的解决方案(如果您没有分离方法或者如果您想共享引用计数)是使用包装类:

template <typename SrcT, typename DestT>
class CastedSmartPtr
{
private:
    SmartPtr<SrcT> ptr;
public:
    CastedSmartPtr(const SmartPtr<SrcT>& src)
    {
        ptr = src;
    }

    DestT& operator* () const
    {
        return *(static_cast<DestT*> >(ptr.get()));
    }

    DestT* operator->() const
    {
         return static_cast<DestT*> >(ptr.get());
    }

    DestT* get() const
    {
        return static_cast<DestT*> >(ptr.get());
    }
}

template <typename DestT, typename SrcT>
inline SmartPtr<DestT> SmartPtrCast(const SmartPtr<SrcT>& src)
{
    return CastedSmartPtr<SrcT, DestT>(src);
}

这将在内部使用SmartPtr(因此正确共享引用计数)和static_cast 在内部使用DestT(不会影响性能)。如果您想使用dynamic_cast,您只能在构造函数中执行一次,以避免不必要的开销。您可能还想向包装器添加其他方法,例如复制构造函数、赋值运算符、分离方法等。

【讨论】:

  • 这会做坏事。你最终会双重破坏指向的东西,因为你不会让两个指针共享相同的引用计数。 (是的,我假设有关 SmartPtrs 的实施的东西......)
  • 假设 B* b = (B*)doSomething().get();B *b = dynamic_cast&lt; B* &gt;( doSomething.get() ); 有效,这应该也可以,这可能不是最好的,但如果您没有任何访问智能指针库的权限,这是你能得到的最好的。如果 SmartPtr detach() 方法或类似的东西,更好的实现是可能的。我会考虑到这一点。
  • 实际上,这个问题是错误的,所以你的答案看起来可以接受。
  • 好吧,再想一想,我承认这可能是有问题的,这取决于具体情况和 SmartPtr 实现(我们对此知之甚少)。包装器解决方案更安全,不会对性能产生任何影响。
【解决方案3】:
SmartPtr<B> b = dynamic_cast<B*>(doSomething().get())

如果您的 SmartPtr 支持,也可以是 doSomething().dynamic_cast&lt;B*&gt;() 之类的东西。

【讨论】:

  • -1,此方法将导致b 的引用计数为1(通常太低,但恰好在这种情况下有效)
  • 为什么是-1?考虑到问题,这是你能做的最好的事情
  • 看VJo的回答。由于您不能 const 构造具有适当引用计数的智能指针,因此构造原始指针要安全得多。就个人而言,我会使用一个参考,这样可以更清楚地表明生命周期是由其他地方决定的。但迈克尔安德森的评论更有用 - 它在任何体面的智能指针库中都得到了解决。
  • 我不同意,你不是在回答原来的问题,而是在改变它。
  • 这个“修复”假设 doSomething() 返回的东西有 referencecount == 1。如果其他人持有对 doSomething() 返回的任何东西的引用,将有两个 SmartPtrs 引用同一个实例 (因此,它将被销毁两次)。
猜你喜欢
  • 2021-04-22
  • 2011-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-08
  • 2013-11-17
相关资源
最近更新 更多