首页 > 解决方案 > 添加字符串类型的类成员会导致调用基类函数而不是子类

问题描述

为什么下面的代码打印 0,但是如果你注释掉“std::string my_string”它会打印 1?

#include <stdio.h>
#include <iostream>

class A {
  public:
    virtual int foo() {
      return 0;
    }
  private:
    std::string my_string;
};

class B : public A {
  public:
    int foo() {
      return 1;
    }
};

int main()
{
    A* a;
    if (true) {
      B b;
      a = &b;
    }
    std::cout << a->foo() << std::endl;
    return 0;
}

我也明白将 std::string 更改为 std:string* 也会导致代码打印 1,删除 if 语句也是如此,尽管我不明白为什么其中任何一个都是真的。

编辑:这似乎是由于一个悬空指针。那么在 C++ 中在 Java 中做这样的事情的标准模式是什么:

Animal animal; 
boolean isDog = false; 
// get user input to set isDog 
if (isDog) { 
  animal = new Dog();
} else {
  animal = new Cat();
}
animal.makeNoise(); // Should make a Dog/Cat noise depending on value of isDog.

标签: c++inheritancepolymorphism

解决方案


问题

该程序具有未定义的行为b仅在if. 访问悬空指针时不能指望逻辑结果。

int main()
{
    A* a;
    if (true) {
      B b; // b is scoped by the body of the if.
      a = &b;
    } // b's dead, Jim.
    std::cout << a->foo() << std::endl; // a points to the dead b, an invalid object
    return 0;
}

TL;DR 解决方案

int main()
{
    std::unique_ptr<A> a; // All hail the smart pointer overlords!
    if (true) {
      a = std::make_unique<B>();
    }
    std::cout << a->foo() << std::endl;
    return 0;
} // a is destroyed here and takes the B with it. 

解释

您可以指向a具有动态生命周期的对象

int main()
{
    A* a;
    if (true) {
      a = new B; // dynamic allocation 
    } // b's dead, Jim.
    std::cout << a->foo() << std::endl; 
    delete a; // DaANGER! DANGER!
    return 0;
}

不幸delete a;的是,它也是未定义的行为,因为A它具有非virtual析构函数。如果没有虚拟析构函数,指向的对象a将被销毁为一个A,而不是一个B

解决方法是提供A一个虚拟析构函数以允许它销毁正确的实例。

class A {
  public:
    virtual ~A() = default;
    virtual int foo() {
      return 0;
    }
  private:
    std::string my_string;
};

无需修改B,因为一旦声明了函数virtual,它就会保留virtual给它的孩子。留意final

最好避免原始动态分配,因此我们可以进行另一项改进:使用智能指针

这让我们回到了解决方案。

文档std::unique_ptr

文档std::make_unique


推荐阅读