【问题标题】:Force deriving class to override data member强制派生类覆盖数据成员
【发布时间】:2015-06-15 15:01:35
【问题描述】:

我有一个虚拟类A,数据为valval2valA 设置,但val2 应该由A 的子级设置(基于val 的值)。我想强制每个派生类设置val2。以下

#include<iostream>

class A {
  public:
    A(): val(1), val2(getVal2())
    {};

    int val;
    int val2;

    protected:
      virtual int getVal2() = 0;
  };

class B: public A {
  protected:
    virtual int getVal2() { return 2*val; };
};

int main(){
  B b;
  std::cout << b.val2 << std::endl;
}

工作,因为A 的构造函数调用了一个当时尚未定义的函数(getVal2):

/tmp/cc7x20z3.o: In function `A::A()':
test9.cpp:(.text._ZN1AC2Ev[_ZN1AC5Ev]+0x1f): undefined reference to `A::getVal2()'
collect2: error: ld returned 1 exit status

强制派生类显式设置val2 的更好方法是什么?

【问题讨论】:

  • 不是B::getVal2() 还没有“定义”(它是);而是调用了 A::getVal2(),因为 A 子对象仍在构建中。
  • 你不需要像在 A 的构造函数和 B 的方法 getVal2() 那样在函数定义的末尾使用分号 ;

标签: c++


【解决方案1】:

在基类的构造函数中将val2 设为参数(并且不要使用默认构造函数):

class A
{
  public:
    A(int _val2) : val(1), val2(_val2) {};

    A() = delete;     //for clarity, not required as it is implicitly deleted

    //...
    int val;
    int val2;
};

这需要派生类在其构造函数中设置val2

struct B : public A
{
    B() : A(0) {}   //A must be initialized, thus val2 is set in any case
    // ...
};

不要尝试访问基类构造函数中的纯虚成员:它们尚未构造,因此还不能访问;这会产生undefined behaviour


编辑:从 cmets 看来,问题实际上比 OP 中描述的更复杂。即:val 应在派生类构造函数初始化器列表中可用(并且不应是静态的)。

我想出的最干净的解决方案(对于其他人请参见 cmets)是引入另一个基类并派生virtual

struct Abase
{
    Abase() : val(1) {}
    int val; 
};

struct A : virtual Abase
{
    A(int _val2) : val2(_val2) {}
    int val2;
};

struct B : virtual A
{
    B() : /* Abase() is called implicitly here, */ A(2*val){}  
                                                 //^^^^^ now val is correctly initialized.
};

DEMO.

此外,您可以考虑继承protected(因为至少Abase 是一个实现细节,并不意味着要多态地使用)。

【讨论】:

  • B的构造函数中,如何确保val(在B::getVal2()中使用的)被初始化之前 getVal2()触发?跨度>
  • @Nico Schlömer:在您的基类中将val 放在val2 之前(请参阅here)。
  • 你能说明val如何用于构造_val2吗?
  • 嗯,我不这么认为:val*2 被执行之前 A,因此val 被初始化。
  • 这不是那么容易(虽然它工作)。原因是当您在基类中调用A(val*2)(设置val2)时,它首先评估所有参数(此时val 尚未初始化)。简单的解决方案:在B 的构造函数的主体中设置val2。 (我会考虑其他人)。
【解决方案2】:

val 可用于val2 的初始化这一事实使事情变得非常复杂。一种解决方案是将val2 传递给A 的构造函数,并创建一个承载val 的父类Abase,并确保它在链中首先被初始化。

另一种选择是在val2 中为A 提供一个纯虚拟getter:

#include<iostream>

class A {
  public:
    A(): val(1)
    {};

    int val;

    virtual int getVal2() = 0;
  };

class B: public A {
  public:
    virtual int getVal2() { return 2*val; };
};

int main(){
  B b;
  std::cout << b.getVal2() << std::endl;
}

【讨论】:

  • 虽然这是你自己的问题,但我感觉你的回答没有回答。据我了解,您问题的核心是如何解决A 中的错误val2(getVal2()) 调用。然而,在你的回答中,你只是放弃了那一段。
  • 感谢您的反馈!没错,这并不强制每个派生类都设置val2。然而,这是一个实现相同效果的简单解决方法,即每个派生类都提供val2(尽管通过getter 而不是直接提供数据——太糟糕了)。由于这很简单并且满足所有其他要求(例如,val 可用于计算val2),我想我会发布它。
猜你喜欢
  • 2014-05-10
  • 1970-01-01
  • 2019-11-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-11
  • 2011-06-22
  • 2021-06-14
相关资源
最近更新 更多