【问题标题】:Casting from Base-Class to Derrived-Class with ``shared_ptr`` behavior使用“shared_ptr”行为从基类转换为派生类
【发布时间】:2021-04-12 23:17:16
【问题描述】:

我无法理解shared_ptr 的转换。 This thread 很好地解释了使用普通指针的行为,结果非常直观 - 正是我所期望的。但是,shared_ptr 显示不同的结果 - 我创建了 3 个类,一个 Base 和两个 Derived,并使用 ***_ptr_cast 玩了一下:

#include <iostream>
#include <memory>

using namespace std;

struct Base 
{
    virtual void f() { cout << "Base" << endl; }
    
    string name = "Base";
};

struct FirstDerived : public Base
{
    void f() override { cout << "FirstDerived" << endl; }
    
    void firstDerived() { cout << "FirstDerived only method" << endl; }
    
    string name = "FirstDerived";
};

struct SecondDerived : public Base
{
    void f() override { cout << "SecondDerived" << endl; }
    
    void secondDerived() { cout << "SecondDerived only method" << endl; }
    
    string name = "SecondDerived";
};


int main()
{
    FirstDerived fd;
    std::shared_ptr<Base> bf = make_shared<Base>(fd);
    std::shared_ptr<FirstDerived> fdp = std::static_pointer_cast<FirstDerived>(bf);
    
    if (fdp)
    {
        fdp.get()->f();
        fdp.get()->firstDerived();
        //cout << fdp.get()->name;
    }
    
    FirstDerived sd;
    std::shared_ptr<Base> bs = make_shared<Base>(sd);
    std::shared_ptr<SecondDerived> sdp = std::static_pointer_cast<SecondDerived>(bs);
    
    if (sdp)
    {
        sdp.get()->f();
        sdp.get()->secondDerived();
        //cout << sdp.get()->name;
    }
   
   return 0;
}

这个程序的输出显示(有趣的是cout &lt;&lt; fdp.get()-&gt;name; 是不可能的,因为它会出现段错误):

Base
FirstDerived only method
Base
SecondDerived only method

进一步研究后我得出结论,static_cast 可能是错误的演员,所以我与dynamic_cast 交换了他们。然而,即使我正在转换为正确的派生版本,动态版本也永远不会返回有效值。 期望的结果是,只有当初始对象的类型与下一步要转换的对象的类型相同时,才会有结果。否则它应该为空。

我应该如何正确执行此操作,是否有我正在搜索的内容的演员表?

【问题讨论】:

  • “即使我正在转换为正确的派生版本” - 是的。如果不是,则您没有指向正确派生版本的对象。您在测试中的先决条件是错误的。重新评估您的断言。
  • StoryTeller 是对的。问题似乎是您假设make_shared&lt;Base&gt;(fd); 复制了fd。它没有。它使用可以采用fdBase 构造函数创建一个Base 对象。那恰好是Base::Base(base const&amp;),复制构造函数,因为fd 有一个Base 子对象。
  • 我明白了,谢谢。那么以目前的状态绝对没有办法实现我想要的输出?
  • 其实有一个很简单的方法:用make_shared&lt;FirstDerived&gt;(fd)复制整个fd。请记住,您的演员正在处理共享对象 *bf,而不是用于创建 *bf 的 ctor 参数。
  • @Nestroy 我更新了我的答案。

标签: c++ inheritance casting shared-ptr


【解决方案1】:

您需要将std::make_shared中的类型分别更改为FirstDerivedSecondDerived。 此外,sd 的类型应为 SecondDerived

int main()
{
    FirstDerived fd;
    std::shared_ptr<Base> bf = make_shared<FirstDerived>(fd);
    std::shared_ptr<FirstDerived> fdp = std::static_pointer_cast<FirstDerived>(bf);
    
    if (fdp)
    {
        fdp.get()->f();
        fdp.get()->firstDerived();
        cout << fdp.get()->name;
    }
    
    SecondDerived sd;
    std::shared_ptr<Base> bs = make_shared<SecondDerived>(sd);
    std::shared_ptr<SecondDerived> sdp = std::static_pointer_cast<SecondDerived>(bs);
    
    if (sdp)
    {
        sdp.get()->f();
        sdp.get()->secondDerived();
        cout << sdp.get()->name;
    }
   
   return 0;
}

这是live demo

编辑 根据 OP 的要求(example application 没有智能指针),他希望将智能指针添加到向量并动态调度存储的类型。为此,可以简单地创建一个向量std::vector&lt;std::shared_ptr&lt;Base&gt;&gt; vec 并通过vec.push_back(std::make_shared&lt;FirstDerived&gt;(fd))vec.push_back(std::make_shared&lt;SecondDerived&gt;(sd)) 添加元素。这是example

int main()
{
    FirstDerived fd;
    SecondDerived sd;

    vector<std::shared_ptr<Base>> vec;
    
    vec.push_back(std::make_shared<FirstDerived>(fd));
    vec.push_back(std::make_shared<SecondDerived>(sd));
    
    auto fdp = std::dynamic_pointer_cast<FirstDerived>(vec[0]);
    auto sdp = std::dynamic_pointer_cast<FirstDerived>(vec[1]);
    
    // true
    if (fdp)
    {
        fdp->f();
        fdp->firstDerived();
        cout << fdp->name;
    }
    
    // false
    if (sdp)
    {
        sdp->f();
        cout << sdp->name;
    }
   
   return 0;
}

这给出了所需的输出:

FirstDerived
FirstDerived only method
FirstDerived

【讨论】:

  • 遗憾的是,这不是我要寻找的。我无法更改模板参数,因为我想将Node3d 的许多不同派生类存储在一个向量中。
  • @Nestroy 我更新了我的答案。只需将std::make_shared&lt;FirstDerived&gt;(fd) 推送到std::vector&lt;std::shared_ptr&lt;Base&gt;&gt;
  • 感谢您的工作,但仍有问题。让我进一步解释一下我的用例:我有一个配置 XML 文件,其中描述了许多对象。在我的例子中,其中定义的所有对象都派生自基类。现在,当我遍历 XML 文件时,我将所有这些对象保存在 Base 类型的向量中。由于这应该完全动态地工作,我不能在编译之前简单地更改类型。
  • @Nestroy 这听起来像是工厂模式的工作。
  • @Nestroy 我相应地更改了您的工厂here。它现在使用智能指针,但您可以轻松调整它以返回普通指针。
猜你喜欢
  • 2018-02-25
  • 2017-01-08
  • 2013-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-17
相关资源
最近更新 更多