向下传送带有附加功能的派生类的共享指针 - 这样安全吗?

时间:2011-06-08 20:37:45

标签: c++ stl shared-ptr downcast

考虑以下概要:

class Base { /* ... */ };

class Derived : public Base
{
public:
    void AdditionalFunctionality(int i){ /* ... */ }
};

typedef std::shared_ptr<Base> pBase;
typedef std::shared_ptr<Derived> pDerived;

int main(void)
{
    std::vector<pBase> v;
    v.push_back(pBase(new Derived()));

    pDerived p1(  std::dynamic_pointer_cast<Derived>(v[0])  ); /* Copy */
    pDerived p2 = std::dynamic_pointer_cast<Derived>(v[0]);    /* Assignment */

    p1->AdditionalFunctionality(1);
    p2->AdditionalFunctionality(2);

    /* A */

    return 0;
}

这里我使用派生类扩展基类,增加了功能(AdditionalFunctionality方法)。

第一个问题,这样可以吗?我已经阅读了很多问题,说这不合适,你应该在基类中声明附加功能(通常建议在基类中使它们成为纯虚方法)。但是,我不想这样做。我想扩展基类的功能,而不仅仅是以不同的方式实现它。是否有更好的解决方案来实现这一目标?

好的,所以在这段代码中我也使用一个STL容器来存储这些指针,这些指针允许我存储指向Base类型对象的指针以及Derived类型的对象,而不会切割对象。

第二个问题,这是有道理的,对吧?事实上,我通过使用指向基类对象的指针而不是基类对象本身来避免切片?

如果我“知道”某个指针指向Derived对象,那么我使用std::dynamic_pointer_cast来转换智能指针。

第三个问题,这个编译没有任何警告和工作,但它是否安全?有效?它会破坏共享指针的引用计数方面,并且在我预期之前无法delete我的对象或delete它们吗?

最后,我可以使用复制构造函数或通过赋值执行此转换,如p1和p2所示。有没有一种首选/正确的方法呢?

类似的问题:

  • Downcasting shared_ptr<Base> to shared_ptr<Derived>?:这是非常接近的,但是这个类没有像我一样增加额外的功能,所以我不确定它是完全一样的。此外,它使用boost::shared_ptr我正在使用std::shared_ptr(虽然我理解提升捐赠了shared_ptr到std库,所以它们可能是相同的。)

感谢您的帮助。


修改

我问的一个原因是我意识到可以完成以下任务(错误):

    /* Intentional Error */
    v.push_back(pBase(new Base()));
    pDerived p3( std::dynamic_pointer_cast<Derived>(v[1]) );
    p3->AdditionalFunctionality(3); /* Note 1 */

我尝试将指向Base对象的指针向下转换为Derived对象的指针,然后调用仅在Derived类中实现的方法。换句话说,指向的对象没有定义(或者甚至没有“意识到”该方法)。

编译器没有捕获到它,但可能会导致段错误,具体取决于AdditionalFunctionality的定义方式。

3 个答案:

答案 0 :(得分:9)

Base是否有虚拟析构函数?如果是,那么使用向下转换是安全的。如果您的错误样本中pDerived的结果应为NULL,那么您每次都需要检查dynamic_pointer_cast的结果。

答案 1 :(得分:3)

如果容器中永远不应该有基础对象(我无法通过编辑暗示问题),那么你应该让容器保持派生对象,然后你可以自动访问附加功能

如果容器可以包含两种类型的对象,那么您似乎希望能够将所有对象视为该容器中的基类。在这种情况下,您几乎肯定希望使用多态来做正确的事情:拥有一个基本上说“做这个工作”的虚拟接口,而父版本可能什么都不做。然后,该方法的子版本实现了您需要的其他功能。

我认为你的代码气味可能与你想象的相关性较低。您是继承重用还是允许替换?您可能还想重新考虑您的公共界面的样子。

所有这一切,如果你决定继续你当前的设计(我至少会强烈评论)我认为你的向下转换应该是安全的,只要你在使用它之前检查非null的动态转换结果

答案 2 :(得分:2)

好的,首先,如果你这样做,你需要确保Base有一个虚拟析构函数。否则,当向量超出范围时,您将获得未定义的行为。 (向量的析构函数将为每个元素调用Base的析构函数。如果任何元素实际上是Derived - KABOOM!)除此之外,你所写的内容是完全安全有效的

但重点是什么?如果你有一个容器的容器,你希望能够对它们进行相同的处理。 (循环遍历所有这些并在每个上调用一个函数。)如果你不想对它们进行相同处理,为什么要将它们放在一个容器中?所以你有一个可以保存指向Base的指针或指向Derived的指针 - 你怎么知道哪个元素属于哪种类型?您是否打算每次要调用dynamic_cast时调用AdditionalFunctionality每个元素,以检查确保元素确实指向Derived?这既不高效也不惯用,它基本上都没有使用继承的全部意义。您可能只是使用标记的联合。

你正在使用错误的工具来完成工作。当人们告诉你不要这样做时,并不是因为它不安全无效,这是因为你最终会使你的代码变得比它需要的更复杂是。