【问题标题】:Why doesn't the function cast a pointer correctly (from base class to derived class)为什么函数不能正确转换指针(从基类到派生类)
【发布时间】:2017-10-25 22:44:44
【问题描述】:

让函数将指向基类型的指针转​​换为指向派生类型的指针的最佳方法是什么?为什么以下不自动工作?

#include <iostream>
#include <memory>
#include <queue>

class Base{
public:
    Base() {};
};

class Derived : public Base{
public:
    Derived() : Base() {};
};

void foo(const std::shared_ptr<Derived>& dp){ // "wrong" signature
    std::cout << "it runs\n";
}

int main(int argc, char **argv)
{
    std::queue<std::shared_ptr<Base>> q;
    q.push( std::make_shared<Derived>() );
    foo(q.front()); // error

    return 0;
}

用以下作品替换foo。但是,这可能不会强制将参数强制为 Derived 类,这是我正在编写的程序中唯一应该存在的东西。

void foo(const std::shared_ptr<Base>& dp){
    std::cout << "it runs\n";
}

我也可以手动投射指针。还有其他想法吗?

编辑: 一些人建议我使用虚函数。我想他们的意思是我应该把foo 作为一个虚拟函数放在类中,如下所示。但是,如果我错了,请纠正我的理解。

#include <iostream>
#include <memory>
#include <queue>

class Base{
public:
    Base() {};
    virtual void foo () { std:: cout << "base\n"; };
};

class Derived : public Base{
public:
    Derived() : Base() {};
    void foo() {std::cout << "derived\n";};
};

int main(int argc, char **argv)
{
    std::queue<std::shared_ptr<Base>> q;
    q.push( std::make_shared<Derived>() );
    q.front()->foo();

    return 0;
}

编辑 2: 谢谢大家的帮助。目前我已经接受了一个答案,但在我的实际程序中,我可能会倾向于使用@alain 的铸造答案,实际上。在这个程序中,我的队列充满了我一次弹出一个的事件。除了这个队列,我还有多个事件处理程序——根据它是什么类型的派生类,我将使用不同的事件处理程序函数。所以我认为这些事件拥有事件处理函数没有多大意义。实际上,这让我觉得我需要重新考虑在事件中完全继承。如果有一些队列允许不同类型的对象,我根本不会遇到这个问题。不过,也许由于其他原因这很糟糕,我不确定。

【问题讨论】:

  • 记住:所有Derived 都是Base,但并非所有Base 都是Derived
  • “最好”的方式是使用virtual函数。
  • 当你不想共享所有权时,不要使用 shared_ptr 作为函数参数。
  • @Taylor 你几乎把它钉在那里。您唯一缺少的想法是Base 的虚拟析构函数。在删除Base 时,需要虚拟析构函数以允许系统追踪并销毁正确的对象。忽略这一点会导致可怕的未定义行为,其中可能会发生许多神奇的事情,从看起来可以工作的程序到组建机器大军并消灭地球上所有有机生命的程序。
  • 我明白你的困惑。我的第一条评论是为了回应 Taylor 开始尝试 virtual 并询问他们是否有任何问题的编辑。

标签: c++ pointers shared-ptr


【解决方案1】:
std::queue<std::shared_ptr<Base>> q;

是一个指向Bases的(共享)指针队列,所以

q.front()

将返回std::shared_ptr&lt;Base&gt;

void foo(const std::shared_ptr<Derived>& dp)

需要std::shared_ptr&lt;Derived&gt;

虽然BaseDerived 之间存在关系,但std::shared_ptr&lt;Base&gt;std::shared_ptr&lt;Derived&gt; 之间没有关系。它们是两种不同类型的shared_ptr

“那么,”你可能会想,“我将shared_ptr::get 指向指针。赢了!”你可以这么想,但是……不。 DerivedBase,但 Base 不是 Derived

此时您有两个选择:将Base 转换为Derived,因为您很清楚它是Derived,但这仅在您知道它是@987654340 的玩具代码中才真正可行@。真正的代码变得更加混乱。这是个坏主意。

那么让我们直接跳到好主意,好吗?虚函数。

#include <iostream>
#include <memory>
#include <queue>

class Base
{
public:
    // none of the constructors do anything, so I got rid of them.
    // code that's not there has no bugs.
    virtual ~Base() = default; // must provide virtual destructor so that
                               // correct destructors get called when a Base
                               // is destroyed
    virtual void foo() = 0; // pure virtual function. All descendants must
                            // provide this function
                            // Pure virtual has the side effect of  making 
                            // it impossible to instantiate a Base.
                            // This may or may not be what you want. If it 
                            // isn't, remove the =0 and implement the function.
/*
    virtual void foo()
    {
        std::cout << "I pity the foo who derives from Base.\n"
    }
*/
};

class Derived: public Base
{
public:
    void foo() // implement Base::foo
    {
        std::cout << "it runs\n";
    }
};


int main()
{
    std::queue<std::shared_ptr<Base>> q;
    q.push(std::make_shared<Derived>());
    q.front()->foo();

    return 0;
}

【讨论】:

  • 嗯...我们还没有看到程序,所以很难给出设计建议。但我的答案当然是快速的 :)
【解决方案2】:

需要进行两项更改:

通过将virtual 函数添加到Base 类来启用多态性。有一个虚拟析构函数是个好主意,所以

virtual ~Base() {};

然后,std::shared_ptr&lt;Base&gt; 无法自动转换为 std::shared_ptr&lt;Derived&gt;。你必须使用dynamic_pointer_cast

foo(dynamic_pointer_cast<Derived>(q.front())); // error

还请检查这是否真的需要,因为不需要从基础转换到派生的设计通常更好。

Working code online

【讨论】:

    【解决方案3】:

    您需要在您的基类中有一个 Virtual Function,否则它会将其视为 Inheritance 而不是 Polymorphism 所以它不会引用您的带有基类指针的子对象。

    【讨论】:

    • 所以如果我在其中放置一个虚拟函数,它不仅会解析对该虚拟函数的调用,还会解析对象类型?
    • 由于该功能,它将能够区分是否应将孩子视为其形式之一的功能。所以,是的,正如 Justin 已经建议的那样,使用虚函数将解决您的问题。
    • 在你的基类和子类中通用的函数,你应该在你的基类中声明它们为virtual,并根据你的功能在子类中覆盖它们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-23
    • 1970-01-01
    • 1970-01-01
    • 2016-12-31
    相关资源
    最近更新 更多