13

考虑以下大纲:

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 所示。有没有首选/正确的方法?

类似的问题:

  • 将 shared_ptr<Base> 向下转换为 shared_ptr<Derived>?:这非常接近,但是衍生类没有像我的那样添加额外的功能,所以我不确定它是否完全一样。此外,它使用boost::shared_ptr我正在使用的地方std::shared_ptr(尽管我理解 boost 将 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定义方式。

4

3 回答 3

9

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

于 2011-06-08T21:05:28.143 回答
3

如果容器中不应该有基础对象(我无法从问题中看出这一点,但您的编辑暗示了这一点),那么您应该让容器包含派生对象,然后您可以自动访问附加功能。

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

我认为您可能有一种代码气味,即您的对象的相关性比您想象的要少。你继承是为了重用,还是允许替换?您可能还想重新考虑您的公共界面的外观。

综上所述,如果您决定继续当前的设计(我至少会强烈审查),我认为您的向下转换应该是安全的,只要您在使用它之前检查动态转换的结果是否为非空。

于 2011-06-08T21:24:59.117 回答
2

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

但有什么意义呢?如果您有一个对象容器,您希望能够一视同仁地对待它们。(遍历所有这些并在每个上调用一个函数,或其他什么。)如果您不想对它们一视同仁,为什么将它们放在一个容器中?所以你有一个向量可以保存指向 Base 的指针或指向 Derived 的指针——你怎么知道哪些元素属于哪种类型?您是否打算dynamic_cast每次要调用AdditionalFunctionality检查以确保元素确实指向 a时只调用每个元素Derived?这既不高效也不惯用,它基本上破坏了使用继承的全部意义。您还不如只使用标记的联合。

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

于 2011-06-08T21:06:42.200 回答