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:
ISelfMovingTransport
and ICar
: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.
std::vector<Transport*>
and cast objects to Car
.
It worked, but I did not like this solution, because it is hard to check if everything is
correct.std::variant<ICECar, ElectricCar>
and std::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.
I believe this addresses your question but hard to tell. It is stripped of some of the details in your code but demonstrates the technique. It uses std::variant
and std::visit
.
#include <iostream>
#include <memory>
#include <variant>
class Car {
public:
Car( ) = default;
};
class ICECar : public Car {
public:
ICECar( ) {
}
void visitCarService( ) {
std::cout << "ICE visitCarService\n";
}
};
class ECar : public Car {
public:
ECar( ) {
}
void visitCarService( ) {
std::cout << "E visitCarService\n";
}
};
using car_variant = std::variant<
std::shared_ptr<ICECar>,
std::shared_ptr<ECar>>;
template <size_t C>
void visitCarService(std::array<car_variant, C>& cars) {
for (auto& c : cars) {
std::visit(
[](auto&& arg) {
arg->visitCarService( );
},
c);
}
}
int main(int argc, char** argv) {
ICECar ice_car { };
ECar e_car { };
std::array<car_variant, 2> cars {
std::make_shared<ICECar>(ice_car),
std::make_shared<ECar>(e_car)
};
visitCarService(cars);
return 0;
}
This compiled using GCC 11 using std=c++17 with -pedantic set. Presumably it should compile under MS. Here is a an online run of it.