覆盖基类成员变量的初始值

Posted

技术标签:

【中文标题】覆盖基类成员变量的初始值【英文标题】:Override initial value of a member variable of base class 【发布时间】:2016-01-04 13:33:47 【问题描述】:

我现在对继承很困惑。我计划简单地覆盖变量的初始值。在下面的代码中,我只是继承了基类并尝试获取它的名称,该名称与类一起保存为字符串。我希望派生类可以覆盖这个值,但它没有这样做。

我的预期输出是

Derived
Derived

但是我得到了

Base
Base

以下实现的正确方法是什么?

#include <iostream>
#include <string>

struct Base 
    virtual ~Base() = default;
    virtual void id()
        std::cout << id_ << std::endl;
    
    std::string id_ = "Base";
;   



struct Derived : public Base 
    virtual ~Derived() = default;
    std::string id_ = "Derived";
;   


int main()
    Base* b = new Derived();
    Derived* d = new Derived();
    b->id();
    d->id();
    delete d;
    delete b;                                                                                                          
    return 0;

【问题讨论】:

成员变量不能是虚拟的(即可覆盖),只能是方法。 【参考方案1】:

以下实现的正确方法是什么?

听起来很难,这实际上取决于您想要实现的目标。

我猜测我们只是在询问一个对象它是什么类型,而不管我们调用的是哪个接口:

struct Base 
  virtual ~Base() = default;

  virtual const std::string id() const 
    return "Base";
  
;

struct Derived : Base 
  virtual const std::string id() const override 
    return "Derived";
  
;

这是另一种方式:

struct Base 
  virtual Base(std::string ident = "Base") 
  : _id(std::move(ident))
  

  virtual ~Base() = default;

  std::string& id() const 
    return _id;
  

private:
  std::string _id;

;

struct Derived : Base 
  Derived() : Base("Derived") 
;

另一个,使用值是接口,但注意这将禁用赋值运算符

struct Base 
  virtual Base(std::string ident = "Base") 
  : id(std::move(ident))
  

  virtual ~Base() = default;

  const std::string id;

;

struct Derived : Base 
  Derived() : Base("Derived") 
;

这个列表绝不是详尽的。

【讨论】:

但这是一个非常好的开始 :) 它为 OP 提供了几种实现这一目标的方法,我相信实验会证明哪一种最适合他们。【参考方案2】:

存在虚拟函数,而不是虚拟成员变量。

您在Derived 类中所做的是定义 一个隐藏其父级的新成员变量id_。但是,id() 函数指的是在该上下文中定义的id_

如果你想覆盖行为,你应该覆盖id()函数:

class Derived: public Base 
    std::string id() const override  return "Derived"; 
;

【讨论】:

没有其他更通用的方法吗? 我推荐了一个。这个或那个更好取决于你的情况,所以权衡一下。【参考方案3】:

为什么不简单地给Base 一个构造函数来设置id 并让Derived 用它的“覆盖”值调用这个构造函数? Boom:根据派生类型的“虚拟数据”。

话虽如此,虚拟方法可能更类似于封装,并为以后更精细的行为提供机会。

编辑:我将其作为评论发布,然后将其转换,此时理查德刚刚击败我发布答案。没有复制!

【讨论】:

【参考方案4】:

由于函数 id() 是虚函数,因此在这两种情况下对象指针都是派生类型。

Base* b = new Derived(); // object type is Derived
b->id();

这里 b 是 Derived 类型的指针 但是“派生”类没有 id() 的实现,因为派生类对象的虚拟查找表中没有 id() 条目,因此它将调用基类函数 id()

其他情况也类似

Derived* d = new Derived(); //object type is Derived
d->id();

由于派生类中没有定义 id() 方法,它只会调用基类 id()。

还有一个疑问是您如何在类中初始化变量“id_”?

【讨论】:

不,该指针非常类似于 Base 类型,而不是 Derived,尽管它可能在运行时通过 vtable 到达。您对初始化id_ 有什么疑问(问题?)?这是大括号或等号初始化,其中成员变量可以被赋予默认值,而不会出现在构造函数初始化列表中。 你的意思是std::string id_ = "Base";在课堂上是正确的??是否可以在类中初始化数据成员(不是静态的)?我在 VS 2012 中尝试过,但它抛出错误“不允许数据成员初始化” VS 经常落后于 C++ 标准的一些新特性,而且这个特性是在 C++11 之后不久出现的,其中添加了这个特性。是的,这是允许的。只是搜索它。 (顺便说一句,这使得类型非 POD)

以上是关于覆盖基类成员变量的初始值的主要内容,如果未能解决你的问题,请参考以下文章

15. 继承重载覆盖隐藏

抽象基类中初始化const成员变量的常用解决方案

如何初始化作为另一个类的成员变量的基类对象?

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

派生类(构造函数)中基类的成员变量初始化顺序

java中证明成员变量有默认初始值