在c++中,如何在基函数中访问派生类的成员?(这是正确的做法吗?)

In C++, how does one access members of a derived class in base functions? (Is this even the correct practice?)

本文关键字:c++ 基函数 访问 派生 成员      更新时间:2023-10-16

我正在用c++编写一个物理程序,使用几种算法来近似对象的范围。我已经声明了一个包含具体和抽象方法的基类Simulation。(例如,近似范围的函数是纯虚拟的,而获取下一个位置的函数是具体的,因为无论算法如何,这都是相同的。)为了定义这些具体的方法,我也在基类中声明了所有的变量。在派生类中,我定义了基类的抽象方法和新的特定于算法的抽象方法;这要求我在基类和派生类中都定义成员。

问题在于基类中定义的具体方法访问的是基类的成员,而不是派生类中定义的被覆盖的成员。有办法做到这一点,还是我的方法本身不正确?

我也有一个类似的问题与构造函数;从派生类的构造函数调用基类构造函数就是初始化基类成员。

无论如何,谢谢你的时间。虽然我对c++并不完全陌生,但我更倾向于把它当作C来使用;我对面向对象的概念缺乏经验。我可能犯了一个非常基本的错误,(或者更可能是设计缺陷),但除了在派生类中重新定义方法之外,我没有发现任何类似的东西,也没有从测试中获得任何积极的结果。(我认为我应该避免这样做)

更新:虽然使成员保护工作,(感谢Ozraptor;不太确定我是怎么错过的)根据R Sahu的要求,我将发布一些我的(现在更新的)代码:

基类:

class Simulation {
 public:
  Simulation();
  Simulation(float, float, float, float, float, float, float, float, float);
  bool IsInitialized(),
       set_air_density(float),
       set_delta_time(float),
       set_drag_coefficient(float),
       set_mass(float),
       set_reference_area(float);
  float next_x_position();
  void set_next_x_position(float),
       set_next_x_velocity(float),
       set_next_y_position(float),
       set_next_y_velocity(float);
 protected:
  static const float gravitational_acceleration_;
  virtual bool SimulateRangeOrDie() = 0;
  virtual void GetNextVelocity() = 0,
               GetNextXVelocity() = 0,
               GetNextYVelocity() = 0,
               InitializeConstant() = 0;
  void GetNextPosition(),
       GetNextXPosition(),
       GetNextYPosition(),
       PushBackPositionVelocity();
  bool x_position_initialized_,
       x_velocity_initialized_,
       y_position_initialized_,
       y_velocity_initialized_;
  float air_density_,
        current_x_position_,
        current_x_velocity_,
        current_y_position_,
        current_y_velocity_,
        delta_time_,
        drag_coefficient_,
        constant_,
        mass_,
        next_x_position_,
        next_x_velocity_,
        next_y_position_,
        next_y_velocity_,
        reference_area_;
};

派生类之一:

class MomentumSimulation : public Simulation {
 public:
  MomentumSimulation();
  MomentumSimulation(float, float, float, float, float, float, float, float,
                     float);
  virtual bool SimulateRangeOrDie();
 private:
  virtual void GetNextVelocity(),
               GetNextXVelocity(),
               GetNextYVelocity(),
               InitializeConstant();
  void GetNextMomentum(),
       GetNextXMomentum(),
       GetNextYMomentum(),
       Initialize(),
       InitializeMomentum(),
       InitializeXMomentum(),
       InitializeYMomentum(),
       PushBack(),
       PushBackMomentum();
  float current_x_momentum_,
        current_y_momentum_,
        next_x_momentum_,
        next_y_momentum_;
};

如果需要访问基类函数实现中的成员,则"提升"这些成员仅为(受保护的)基类成员-您也需要在派生类中声明它们。在这两种情况下,基类和派生类函数都可以直接访问它们。

重建构造函数——由于构造函数调用的顺序,你只能初始化正在构造的实际类的成员,或者它的基类成员。您不能初始化派生类成员,因为它们还不存在!

这是一个宽泛的问题,需要你对继承有更好的理解。但有几件事你必须先知道:

如果你继承了一个类(超类),并且超类有一些具体的方法,你不需要在子类中再次实现它们,除非你想要对那个子类有一个不同的实现。这叫做重写那个方法

如果在子类中有一个被重写的方法,将使用哪些类成员和方法完全依赖于调用该方法的对象的声明:

SuperClass a = new SuperClass();
a.someMethod(); //definitely the one in the superclass is called
SuperClass b = new SubClass();
b.someMethod(); //the one in the SuperClass called
((SubClass)b).someMethod(); //the one in SubClass called
SubClass c = new SubClass(); //trivial

在每个特定算法(每个派生类)中应该被覆盖的基类方法应该声明为virtual。这种方法有时被称为模板方法模式。至于成员数据,尽管你可以在派生类中访问基类受保护的数据成员,而且这通常是编写代码的最快方式,但最干净的方法是不直接从基类访问成员数据,而是使用成员函数来访问或操作这些数据,并调用这些函数。这源于封装OO原则。请参阅:使成员变量受到保护是一种好做法吗?

为了给您一个稍微做作的(c++ 11)示例,下面是一个基本模拟类:
using namespace std;
using namespace chrono;
class Simulation {
  public:
  // this is the same across all simulations:
    void run() {
      recordStartTime();
      bool result = actuallySimulateStuff();
      printSimulationResult(result);
    }
  protected:
  // virtual methods should be overridden in derived classes
  // this one has to be
    virtual const string& name() const = 0;
  // this one may not be, but if you don't, the simulation isn't going tobe very interesting
    virtual bool actuallySimulateStuff() { return false; }
  // these methods, like run() above, are invariant for all inherited classes
    void recordStartTime() {
      start_time_ = system_clock::now();
    }
    const start_time_& start_time() const {
      return start_time_;
    }
    void printSimulationResult(bool result) {
      auto end_time = system_clock::now();
      auto durationInMS = duration_cast<milliseconds>(end_time - start_time_);
      cout << "Simulation: '" << name() << "'";
      cout << (result ? "Succeeded" : "Failed");
      cout  << " in " << durationInMS << "ms."; 
    }
  private:
    system_clock::time_point start_time_ {};
};

这里是一个特定的模拟类:

class TransmogrifySimulation : public Simulation {
  protected:
  // virtual methods should be overridden in derived classes
    virtual const string& name() const {
       static const string name_ = "TransmogrifySimulation";
       return name_;
    }
    virtual bool actuallySimulateStuff() {
       // simulate transmogrification here...
       ...
       // which implies, at some point:
       someSimulationDetail();
       ...
       return result_;
    }
    void someSimulationDetail() {
       // this is a really weird, unreliable simulation
       auto currentRunTime = duration_cast<milliseconds>(system_clock::now() - start_time());
       result_ = 0 != (currentRunTime % 2);
    }
   private:
     bool result_ = false;
};

这只是一个例子,但是如果你想对它所使用的概念感到舒适,我强烈建议你从《权威c++图书指南和列表》中挑选一本初学者指南,并至少阅读与类和继承相关的章节

关于构造函数,它们几乎遵循相同的封装原则:类的构造函数负责初始化在该类层次结构的该级别定义的成员数据。您应该注意以下几点:

    正如您所注意到的,它们只能初始化在其类继承层次结构级别上定义的数据成员。参见用初始化列表初始化父级's受保护成员(c++)你应该避免在构造函数中调用任何虚函数。参见:在构造函数内部调用虚函数