Я пишу физическую программу на 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_;
};
Если необходимо получить доступ к членам из реализаций функций базового класса, «продвигать» эти члены только как (защищенные) члены базового класса — вам также необходимо объявить их в производных классах. Функции базового и производного классов могут обращаться к ним напрямую в обоих случаях.
Re конструкторы — из-за порядка вызова конструктора, вы можете только инициализировать членов создаваемого фактического класса или членов базового класса. Вы не можете инициализировать производные члены класса, так как они еще не существуют!
Это довольно широкий вопрос, требующий лучшего понимания наследования. Но вот несколько вещей, которые вы должны знать в первую очередь:
Если вы наследуете класс (суперкласс), а суперкласс имеет несколько конкретных методов, вам НЕ нужно снова их реализовывать в подклассах, если, конечно, вы не хотите иметь другую реализацию, специфичную для этого подкласса. И это называется переопределением этого метода.
Если в вашем подклассе есть переопределенный метод, то, какие члены класса и методы будут использоваться, полностью зависит от объявления объекта, для которого вы вызвали метод:
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
, Этот подход иногда называют Шаблон шаблона.
Что касается данных-членов, даже если вы можете получить доступ к защищенным элементам данных базового класса в производных классах, и зачастую это самый быстрый способ написания кода, самый простой способ сделать это — не обращаться напрямую к данным-членам базового класса, а иметь функции-члены, которые обращаются к этим данным или манипулируют ими, и вместо этого вызывают их. Это вытекает из принципа ОО инкапсуляции. Увидеть: Это хорошая практика, чтобы сделать переменные-члены защищенными?
Чтобы дать вам немного надуманный пример (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 ++ и прочитайте хотя бы главы, связанные с классами и наследованием
Что касается конструкторов, они в значительной степени следуют одному и тому же принципу инкапсуляции: конструктор класса отвечает за инициализацию данных-членов, определенных на этом уровне иерархии классов. Вы должны отметить несколько вещей: