Let's see example classes. Base class is ITransport
, transport class interface:
class ITransport {
public:
virtual void move(const Path& p) = 0;
virtual double estimateTime(const Path& path) = 0;
/*Some more methods.*/
};
Implementation:
class Transport : public ITransport {
public:
virtual void move(const Path& p) override {
currPoint_ = p.lastPoint();
}
/*Some more methods.*/
private:
Point currPoint_;
};
Let's also imagine we want to create a self moving transport class:
template <typename EnergySource>
class SelfMovingTransport : public Transport {
/*Some special methods for self moving transport.*/
};
The simplest example of self-moving transport is car:
template <typename EnergySource>
class Car : public SelfMovingTransport <EnergySource> {
public:
virtual void visitCarService() = 0;
/*Some more methods with logic for cars.*/
};
Also need to create car with internal combustion engine...
class ICECar : public Car<Petrol> {
public:
virtual void move(const Path& p) override {
Transport::move(p);
/*Some special methods for ICECar.*/
}
virtual void visitCarService() override {
/*Visit closest ICECar service.*/
}
/*Some special methods for ICECar.*/
private:
Petrol::Amount petrol_;
};
... and an electric car class.
class ElectricCar : public Car<Electriсity> {
public:
virtual void move(const Path& p) override {
Transport::move(p);
/*Some special methods for ElectricCar.*/
}
virtual void visitCarService() override {
/*Visit closest ElectricCar service.*/
}
/*Some special methods for ElectricCar.*/
private:
Electricity::Amount charge_;
};
The continuation of this logic can be, for example, adding trains class and etc.:
template <typename EnergySource>
class Train : public SelfMovingTransport<EnergySource> {
/*Not interesting.*/
};
I use c++17
compiller (MS). Not less, not more.
I want to create an array (or std::vector<Car*>
) of
pointers to cars of different types and
call some common methods for them.
For example, to have a simple way to send them all to
the service (see Car::visitCarServeice()
).
I've tried tree ideas:
- Create classes
ISelfMovingTransport
andICar
:
class ISelfMovingTransport : public virtual ITransport { /*All the same.*/ }; class ICar : public virtual ISelfMovingTransport { /*All the same.*/ };
Changed
Transprot
to:class Transport : public virtual ITransport { /* All the same. */ }
Changed
SelfMovingTransport
to:template <typename EnergySource> class SelfMovingTransport : public ISelfMovingTransport, public Transport<EnergySource> {};
Changed
Car
to:template <typename EnergySource> class Car: public ICar, public SelfMovingTransport<EnergySource> { /*All the same*/ };
In the end solution did not work, because
static_cast
can not be used to cast pointer to virtually derived class pointer (See pastebin link.). Example code can't be compiled (error: cannot convert from pointer to base class ‘ISelfMovingTransport’ to pointer to derived class ‘ElectricCar’ because the base is virtual). When I want to make actions withElectricCar
which is accessed as a pointer to aCar
, I needdynamic_cast<ElectricCar*>(carPtr)
wherecarPtr
is ofCar*
. Butdynamic_cast
is not allowed,RTTI
is turned off.
- Use
std::vector<Transport*>
and cast objects toCar
. It worked, but I did not like this solution, because it is hard to check if everything is correct. - Using
std::variant<ICECar, ElectricCar>
andstd::visit
. (std::visit([](auto& car) -> void { car.visitCarServeice(); }, carV)
). (Now implemented with this method.).
In this example (which represents a problem in a real project) I don't want to change logic (especially classes from Transport
level to Car
level).
Is there a common way to do required things without RTTI and dynamic_cast?
Is std::variant
'OK' in this situation (assuming that car classes don't
differ by size and/or memory is not important)?
Asked question, because don't know how to google that.
P.S. All examples are representation (analog, etc...) of a situation in real project. I ask you to imagine that energy type as parameter is really needed and not to think about complications (hybrid cars, etc.).
P.P.S. In the real project I need an only object of a "car" as a field of other class.
ICECar
andElectricCar
are completely unrelated classes and what (if anything) they inherit is their implementation detail.ICECar
can be an empty class and that would be valid. – DevitrifySelfMovingTransport
doesn't make much sense, as not all cars are self-moving, soCar
should not derive fromSelfMovingTransport
. But you might haveSelfMovingCar
that derives fromCar
instead. Also, using a template for the energy type doesn't make sense either. For instance, a Car that has has been upgraded from a Petrol engine to an Electric engine is still a Car. MakingCar
a template just makes it harder to group different types of Cars together. Much of what you are trying to use inheritance for should probably be using encapsulation or Dependency Injection instead. – MellissamellitzCar
is really needed etc..). – Harvillestd::variant
– Supercharge/* not interesting */
and a source of significant design constraints. You are confusing pointers/references and objects. You mention RTTI/dynamic cast as a problem but do not demonstrate it occurring. Your example code is full of typos, does not demonstrate the problem, so we cannot use it to solve or understand your actual problem. – MoussorgskyIVisitService
declarevisitService()
and let all derivedCar
implement it? Then maintain a list of pointers point to instances implementingIVisitService
so you can call them all in a loop. – NurseIVisitService
". It seems to be the most correct method, but not the simplest to implement (one additional class). – Harvillestatic_cast
here? – Telluratestatic_cast
better. – Harvillereinterpret_cast
instead haha – Crim