添加字符串类型的类成员会导致调用基类函数而不是子类

Posted

技术标签:

【中文标题】添加字符串类型的类成员会导致调用基类函数而不是子类【英文标题】:Adding a string-type class member causes base class function to be called instead of child 【发布时间】:2019-10-17 22:22:30 【问题描述】:

为什么下面的代码打印的是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++ 中在 Java 中做这样的事情的标准模式是什么:Animal animal;布尔 isDog = false; // 获取用户输入以设置 isDog if (isDog) animal = new Dog(); 其他 动物 = 新猫(); @Hydra 1) 如果你使用new - 它不会是一个悬空指针,它会工作得很好。注意:不过,您需要delete 它。因此,您与 Java 代码的比较是不等价的(即使它相似)。这就像问为什么苹果和橙子的味道不同,如果它们都是圆形的。 2) C++ 不是 Java。 @AlgirdasPreidžius 如果您使用 new 发布带有我的代码的更正版本的答案,那么我将接受它作为正确答案。抱歉,如果指针仅存在于 if 范围内,我不确定在哪里删除它。 @Hydra 1) 在我写评论时,已经有一个答案,解释了这个问题。我只是响应您的 Java sn-p,不等同于您的 C++ sn-p。 2)“对不起,如果指针仅存在于 if 范围内,我不确定在哪里删除指针。”是什么意思?它没有。这样的问题表明,您不熟悉 C++ 语言基础知识(例如:对象生命周期),您应该向good C++ book 学习它们。 【参考方案1】:

问题

该程序有Undefined Behaviour。 b 仅在 if 的正文范围内。访问dangling pointer 时不能指望逻辑结果。

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。 Keep an eye out for final.

但它是best to avoid raw dynamic allocations,所以我们可以再做一项改进:Use Smart pointers。

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

Documentation for std::unique_ptr

Documentation for std::make_unique

【讨论】:

以上是关于添加字符串类型的类成员会导致调用基类函数而不是子类的主要内容,如果未能解决你的问题,请参考以下文章

从基类访问子类成员的首选方式

C++中如何在子类的构造函数中调用基类的构造函数来初始化基类成员变量

如何将基类的未知子类放在一个数据结构中并在 C++ 中调用重写的基类函数

c++ 虚函数 如何调父类 而非子类

多态(day10)

定义子类对象时要先调用基类构造函数,是应该哪样理解呢