将共享指针向下转型为具有附加功能的派生类 - 这安全吗?

发布于 2024-11-14 10:19:31 字数 2044 浏览 3 评论 0原文

请考虑以下概要:

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 类型的对象的指针,而无需对对象进行切片。

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

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

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

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

类似的问题:

  • 向下转型shared_ptr; to shared_ptr?:这非常接近,但是派生类没有像我的那样添加额外的功能,所以我不确定它是否完全相同。另外,它使用 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 对象的指针,然后调用方法仅在派生类中实现。换句话说,指向的对象没有定义(或者甚至不“知道”该方法)。

这不会被编译器捕获,但可能会导致段错误,具体取决于AdditionalFunctionality的定义方式。

Consider the following outline:

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;
}

Here I'm extending the base class with a derived class that adds functionality (the AdditionalFunctionality method).

First question, is this OK? I've read a lot of questions that say this is not okay and you should declare the additional functionality in the base class (often suggested as making them pure virtual methods in the base class). However, I don't want to do this. I want to extend the functionality of the base class, not just implement it differently. Is there a better solution to accomplish this goal?

Okay, so in this code I am also using an STL container to store these pointers which allows me to store pointers to both objects of type Base as well as objects of type Derived without slicing the objects.

Second question, this makes sense, right? I am, in fact, avoiding slicing by using pointers to base class objects rather than the base class objects themselves?

If I "know" that a certain pointer is to a Derived object, I then use std::dynamic_pointer_cast to cast the smart pointer.

Third question, this compiles without warning and works, but is it safe? Valid? Will it break the reference counting aspect of shared pointers and fail to delete my objects or delete them before I expect?

Lastly, I can do this cast using either the copy constructor or via assignment as shown for p1 and p2. Is there a preferred / correct way of doing this?

Similar questions:

  • Downcasting shared_ptr<Base> to shared_ptr<Derived>? : This is very close, however the dervied class does not add additional functionality like mine does, so I'm not sure it's completely the same. Also, it uses boost::shared_ptr where I'm using std::shared_ptr (although I understand boost donated shared_ptr to the std library, so they're likely the same).

Thank you for your help.


Edit:

One reason I ask is that I realize that the following could be done (incorrectly):

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

Where I attempt to downcast a pointer to a Base object to a pointer of a Derived object and then call a method that is only implemented in the Derived class. In other words, the object pointed to doesn't define (or isn't even "aware of" the method).

This is not caught by the compiler, but may cause a segfault depending on how AdditionalFunctionality is defined.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

傾旎 2024-11-21 10:19:31

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

Does the Base has a virtual destructor? If yes then it is safe to use downcasting. In your incorrect sample pDerived should be NULL in result, so you need to check the result of dynamic_pointer_cast every time.

回心转意 2024-11-21 10:19:31

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

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

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

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

If the container should never have base objects in it (I can't tell from the question but that's implied by your edit) then you should make the container hold derived objects instead, and then you have automatic access to the additional function.

If the container can have both types of objects, then it seems that you want to be able to treat all the objects as the base class within that container. In this case you almost certainly want to use polymorphism to do the right thing: Have a virtual interface that basically says "Do this work" and the parent version may do nothing at all. Then the child version of the method implements the additional functionality you need.

I think you may have a code smell that your objects are less related than you think. Are you inheriting to reuse, or to allow substitution? You may also want to reconsider what your public interface looks like.

All that said, should you decide to continue with your current design (which I would at least strongly review) I think your downcasting should be safe as long as you check the result of the dynamic cast for non-null before using it.

许你一世情深 2024-11-21 10:19:31

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

但有什么意义呢?如果您有一个对象容器,您希望能够以相同的方式对待它们。 (循环所有这些并在每个上调用一个函数,或者其他什么。)如果您不想以相同的方式对待它们,为什么要把它们放在一个容器中?因此,您已经有了一个可以保存指向 Base 的指针或指向 Derived 的指针的向量 - 您如何知道哪些元素属于哪种类型?您是否打算每次调用 AdditionalFunctionality 时都对每个元素调用 dynamic_cast 来检查以确保该元素确实指向派生?这既不高效也不惯用,而且它基本上违背了使用继承的全部意义。您可能还只是使用标记联合。

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

OK, first off, if you do this, you'll want to make sure that Base has a virtual destructor. Otherwise, you'll get undefined behavior when the vector goes out of scope. (The vector's destructor will call Base's destructor for each of its elements. If any elements are really a Derived -- KABOOM!) Other that that, what you've written is perfectly safe and valid.

But what's the point? If you have a container of objects, you want to be able to treat them all the same. (Loop over all of them and invoke a function on each, or whatever.) If you didn't want to treat them the same, why put them in one container? So you've got a vector that can hold pointers to Base or pointers to Derived -- how do you know which elements are of which type? Are you planning to just call dynamic_cast on each element every time you want to call AdditionalFunctionality to check to make sure that the element really points to a Derived? That's neither efficient nor idiomatic, and it basically defeats the whole point of using inheritance. You might as well just be using a tagged union.

You're using the wrong tool for the job. When people told you not to do this, it wasn't because it was unsafe or invalid, it's because you'll just end up making your code more complex than it needs to be.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文