【问题标题】:Override member field in derived classes覆盖派生类中的成员字段
【发布时间】:2013-10-10 08:31:42
【问题描述】:

下面有一个代码sn-p:

#include <iostream>

using namespace std;

class Base {
public:
    Base() : b(0) {}
    int get();
    virtual void sayhello() { cout << "Hello from Base with b: " << b << endl; }
private:
    int b;
};

int Base::get() {sayhello(); return b;} 

class Derived : public Base {
public:
    Derived(double b_):b(b_){}
    void sayhello() { cout << "Hello from Derived with b: " << b << endl; }
private:
    double b;
};

int main() {
    Derived d(10.0);
    Base b = d;

    cout << "Derived b: " << d.get() << endl;
    cout << "Base b: " << b.get() << endl;
}

运行编译后的可执行文件,我发现结果在我的 llvm-g++ 4.2 机器上超出了我的预期。我的盒子上的输出是

Hello from Derived with b: 10
Derived b: 0
Hello from Base with b: 0
Base b: 0

我想要在代码中做的是覆盖Derived 类中的成员字段(b)。 由于我认为BaseDerived 都需要访问这个字段,所以我在Base 中定义了一个get 成员函数,因此Derived 可以继承它。 然后我尝试从不同的对象中获取成员字段。

结果显示,我仍然通过d.get()Base 中获得原始b,而不是在Derived 中,这是我期望代码执行的操作。 代码(或我的理解)有什么问题吗?规范中是否指定了此行为?覆盖成员字段并正确定义其 getter 和 setter 的正确方法是什么?

【问题讨论】:

  • 为什么这个结果会出乎你的意料?除非它是合格的,否则您正在从 D 隐藏 B::b。但是D 的隐藏的、已初始化的 B::b 成员被 B 的默认 copy-ctor 复制,因为它应该是。您的基类不只是通过某种渗透开始使用它们的非隐藏派生类成员变量。他们甚至不知道他们在那里。

标签: c++ c++11


【解决方案1】:

派生类中添加的新b 不会覆盖基类的b。它只是隐藏它。

因此,在派生类中,您有两个b,而虚方法打印出对应的b

【讨论】:

  • 谢谢。我无法解释为什么当我使用d.get() 时,我仍然从Base 获得0,即b 值。
  • 因为get() 不是虚拟的,它返回Base 类中定义的b。它对Derived 类一无所知,因此它显然无法返回Derived::b 成员的值。只有sayhello()是虚拟的,因此它是唯一可以从Derived返回b的方法。
  • 首先你不应该这样隐藏变量。
  • @StephaneRolland 那么正确的方法是什么?你能把这个想法变成一个答案吗?谢谢你:)。
  • 您只覆盖函数,而不是数据成员。
【解决方案2】:

您不能简单地覆盖成员字段,并且在编译 Base::get 时,b 变量被解析为 Base::b,因此此方法将始终使用此值,而不是来自另一个具有相同字段的值派生类中的名称。

覆盖属性的常用方法是覆盖访问它的方式,即覆盖访问器(getter 和 setter)。

你可以通过装饰getter来实现类似的效果,但是getter的返回类型总是一样的:

class Base {
public:
    Base() : b(0) {}
    int get();
    virtual void sayhello() { cout << "Hello from Base with b: " << b << endl; }
protected:
    virtual int getB() {return b;}
private:
    int b;
};

int Base::get() {sayhello(); return getB();} 

class Derived : public Base {
public:
    Derived(double b_):b(b_){}
    void sayhello() { cout << "Hello from Derived with b: " << b << endl; }
protected:
    int getB() override {return b;} // conversion from double to int
private:
    double b;
};

【讨论】:

  • 谢谢。就我而言,getter 代码很简单且“可重用”(只需返回基础数据)。我可以重用来自Base 的代码的任何方法?
  • @Summer_More_More_Tea 你是什么意思?您不想覆盖吸气剂吗?那么直接就不可能了。如果您希望始终在 getter 中完成一些额外的行为,请使用您在装饰 getter 中调用的底层(受保护?)虚拟 getter。我用一个例子编辑我的帖子
【解决方案3】:

我不确定我对你的理解是否正确,但“覆盖”是指“替换”,你会使用模板:

#include <iostream>
using namespace std;

template< typename T >
class Base {
public:
    Base() : b(0) {}
    Base(T b_) : b(b_) {}
    T get();
    virtual void sayhello() { cout << "Hello from Base with b: " << b << endl; }
protected:
    T b;
};

template< typename T >
T Base<T>::get() {sayhello(); return b;} 

class Derived : public Base<double> {
public:
    Derived(double b_):Base(b_){}
    void sayhello() { cout << "Hello from Derived with b: " << this->b << endl; }
};

int main() {
    Derived d(10.0);
    Base<double>* b = &d;

    cout << "Derived b: " << d.get() << endl;
    cout << "Base b: " << b->get() << endl;
}

您在main 中的代码也在尝试Base b = d;,这会导致切片,以上修复了该问题并确保您不会意外使用Base&lt;int&gt; 而不是Base&lt;double&gt;

Live example

【讨论】:

  • 谢谢,这确实是一个解决方案。我无法解释的是,在我的原始代码中,为什么我仍然从Based.get() 得到b
  • @Summer_More_More_Tea 在您的代码中 d.get() 调用非虚拟 int Base::get() 显然访问 Base::b 这是一个 int0 初始化。
  • 即使将get设为virtual也无法解决问题。您应该在派生类中覆盖get
  • @MM。在 OPs 代码中,get() 的返回类型会有所不同,因此您不能简单地覆盖它。或者你必须接受派生的double 被转换为int
【解决方案4】:

你应该重写你的 Derived::ctor 如下:

Derived(double _b)
:Base(_b)
{}

并删除派生类中归档的b。而是将Base 类中的b 标记为受保护。

编辑
无视这一切 我在您的代码中发现了一个问题:

Base b = d;

您正在复制派生对象到基础。它只复制基本字段。如果您想要多态性,请尝试下一个:

Base *b = &d;
b->get()

【讨论】:

  • 谢谢。也许我没有很好地表达自己。我想用另一种类型重新定义Derived中的b,即更改底层数据结构的实现。我明白你的回答,但是,b 这里仍然是int 类型。
猜你喜欢
  • 2014-05-10
  • 1970-01-01
  • 1970-01-01
  • 2011-03-11
  • 1970-01-01
  • 2015-12-11
  • 2011-04-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多