You just experimented slicing.
A Child
is a Parent
plus a few more things.
When you try to put a Child
in the vector of Parent
, only the Parent
part of each of these Child
ren is put in the vector (because the vector does not contain Child
ren but Parent
s); hence the name slicing.
In order to obtain the dynamic polymorphism you are looking for, you need a vector of pointers to Parent
; this way, each pointed-to element can be whether a Parent
or a Child
and behave accordingly.
This is often done with a dynamic allocation of each element in the vector, but this is not mandatory. For example, you could store all the Parent
s in a vector, all the Child
ren 1 in another, and so on, and finally use a vector of pointers to designate some of them in any order.
If you decide to allocate each Parent
/Child
individually, you should consider smart-pointers like std::unique_ptr<T>
rather than raw pointers and new
/delete
.
You will find below your example slightly modified in order to obtain dynamic polymorphism.
It relies on dynamic allocation of each element thanks to std::unique_ptr<T>
/std::make_unique()
.
Note that, because of dynamic polymorphism, you need a virtual
destructor (even if it does nothing special).
Since this hierarchy of types is intended for dynamic polymorphism, it is encouraged to prevent slicing (that you have just experienced) by forbidding the use of copy/move operations.
Thus, you have to provide one or several constructors that fulfil your needs (but this is quite common).
My last advice is « avoid dynamic polymorphism; prefer template
» but it is another topic ;^)
/**
g++ -std=c++17 -o prog_cpp prog_cpp.cpp \
-pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#include <iostream>
#include <string>
#include <vector>
#include <memory> // std::unique_ptr<T>, std::make_unique()
class Parent
{
public:
virtual ~Parent() =default; // ensure correct destruction
// forbid copy and move in order to prevent slicing
Parent(const Parent &) =delete;
Parent &operator=(const Parent &) =delete;
Parent(Parent &&) =delete;
Parent &operator=(Parent &&) =delete;
Parent() =default; // provide a suitable constructor
virtual
const std::string &
getType() const
{
return type;
}
private:
// inline static // is probably better
const std::string type{"parent"};
};
class Child1: public Parent
{
public:
const std::string &
getType() const override
{
return type;
}
private:
// inline static // is probably better
const std::string type{"type1"};
};
class Child2 : public Parent
{
public:
const std::string &
getType() const override
{
return type;
}
private:
// inline static // is probably better
const std::string type{"type2"};
};
int
main()
{
const auto p=Parent{};
std::cout << "p: " << p.getType() << '\n';
const auto c1=Child1{};
std::cout << "c1: " << c1.getType() << '\n';
const auto c2=Child2{};
std::cout << "c2: " << c2.getType() << '\n';
auto people=std::vector<std::unique_ptr<Parent>>{};
for(auto i=0; i<2; ++i)
{
people.emplace_back(std::make_unique<Parent>());
people.emplace_back(std::make_unique<Child1>());
people.emplace_back(std::make_unique<Child2>());
}
for(const auto &e: people)
{
std::cout << e->getType() << '\n';
}
return 0;
}