Reputation: 183
To create instance on the heap and maintain polymorphism, and that'll give the right answer:
class Father
{
public:
virtual void Say()
{
cout << "Father say hello" << endl;
}
};
class Son : public Father
{
public:
void Say()
{
cout << "Son say hello" << endl;
}
};
int main()
{
std::vector<Father*> v;
std::cout << 1 << std::endl;
for(int i(0); i<5; i++)
{
auto p = new Son(); ---------------on heap
v.emplace_back(p);
}
for(auto p : v)
{
p->Say();
}
}
But when I want to create an instance on the stack, it seems not so easy:
Edition 1:
class Father
{
public:
virtual void Say()
{
cout << "Father say hello" << endl;
}
};
class Son : public Father
{
public:
void Say()
{
cout << "Son say hello" << endl;
}
};
int main()
{
std::vector<Father> v;
for(int i(0); i<5; i++)
{
auto o = Son(); ---------------on stack
v.emplace_back(o);---------------now "o" is cast to Father type
}
for(auto o : v)
{
o.Say();------------------------only output "Father say hello"
}
}
And edition 2:
class Father
{
public:
virtual void Say()
{
cout << "Father say hello" << endl;
}
};
class Son : public Father
{
public:
void Say()
{
cout << "Son say hello" << endl;
}
};
int main()
{
std::vector<Father*> v;
for(int i(0); i<5; i++)
{
auto p = &Son(); --------------On the stack
v.emplace_back(p);---------------Now "o" is cast to Father type
}
for(auto p : v)
{
p->Say();------------------------Since "p" now is a Wild pointer, it'll fail too
}
}
Can this be fixed? Or is it just a dead end: If I want to use polymorphism, then I have to create an object on the heap.
Upvotes: 2
Views: 168
Reputation: 98495
The question doesn't have much to do with stack. You're really asking how to implement polymorphism when storing by value. It's not too hard if you can use C++17 and thus have std::variant
available.
The implementation is surprisingly simple:
#include <algorithm>
#include <cassert>
#include <variant>
#include <vector>
enum class Who { Father, Son };
struct ISayer {
virtual Who Me() const = 0;
virtual ~ISayer() {};
};
struct Father final : ISayer {
Who Me() const override { return Who::Father; }
};
struct Son final : ISayer {
Who Me() const override { return Who::Son; }
};
struct AnySayer0 : std::variant<Father, Son>
{
using variant_type = std::variant<Father, Son>;
using variant_type::variant;
operator const ISayer &() const {
return std::visit([](auto &val) -> const ISayer &{ return val; },
static_cast<const variant_type &>(*this));
}
operator ISayer &() {
return std::visit([](auto &val) -> ISayer &{ return val; },
static_cast<variant_type &>(*this));
}
const ISayer *operator->() const { return &static_cast<const ISayer &>(*this); }
ISayer *operator->() { return &static_cast<ISayer &>(*this); }
};
using AnySayer = AnySayer0;
int main()
{
std::vector<AnySayer> people;
people.emplace_back(std::in_place_type<Father>);
people.emplace_back(std::in_place_type<Son>);
assert(people.front()->Me() == Who::Father);
assert(people.back()->Me() == Who::Son);
}
An alternate implementation AnySayer1
would need a bit more boilerplate, and perhaps be a bit faster - but would it be a bit smaller as well?
struct AnySayer1
{
template <typename ...Args>
AnySayer1(std::in_place_type_t<Father>, Args &&...args) :
father(std::forward<Args>(args)...), ref(father) {}
template <typename ...Args>
AnySayer1(std::in_place_type_t<Son>, Args &&...args) :
son(std::forward<Args>(args)...), ref(son) {}
~AnySayer1() { ref.~ISayer(); }
operator const ISayer &() const { return ref; }
operator ISayer &() { return ref; }
const ISayer *operator->() const { return &static_cast<const ISayer &>(*this); }
ISayer *operator->() { return &static_cast<ISayer &>(*this); }
AnySayer1(AnySayer1 &&o) : ref(getMatchingRef(o)) {
if (dynamic_cast<Father*>(&o.ref))
new (&father) Father(std::move(o.father));
else if (dynamic_cast<Son*>(&o.ref))
new (&son) Son(std::move(o.son));
}
AnySayer1(const AnySayer1 &o) : ref(getMatchingRef(o)) {
if (dynamic_cast<Father*>(&o.ref))
new (&father) Father(o.father);
else if (dynamic_cast<Son*>(&o.ref))
new (&son) Son(o.son);
}
AnySayer1 &operator=(const AnySayer1 &) = delete;
private:
union {
Father father;
Son son;
};
ISayer &ref;
ISayer &getMatchingRef(const AnySayer1 &o) {
if (dynamic_cast<const Father *>(&o.ref))
return father;
if (dynamic_cast<const Son *>(&o.ref))
return son;
assert(false);
}
};
This could be rewritten using the same "magic" that makes std::variant
work - it'd be less repetitive that way.
But - is it smaller?
static_assert(sizeof(AnySayer1) == sizeof(AnySayer0));
No. At least in both gcc and clang, both implementations have the same size. And that makes sense, since std::variant
doesn't need to store any more information than we do - it only needs to keep some way to discriminate the type. We chose to use the reference to ISayer
and use dynamic type information, since that optimizes for the common case when we convert to the interface type - we store the reference ready to use. std::variant
can't assume that the types have a common base, and instead stores an integer type index instead and uses generated code to dispatch on that index. It may be generally a bit slower in the path that uses the visitor to return the reference - but not necessarily, since the compiler can note that both types have their ISayer
vtable pointer at the same location, and can squash the type-based dispatch down to a "has value vs has no value" test. It seems that the most recent versions of all major C++ compilers (gcc, clang and MSVC) easily deal with this and generate code that is just as fast as our "optimized" AnySayer1
.
Upvotes: 0
Reputation: 122840
In general polymorphism does not require dynamic allocations. That's a common misunderstanding, and hence here comes a counter example:
void foo(const Father& f) { f.Say(); }
Son s;
foo(s);
You have to declare Say
as const
to make it work, but then it will print the expected Son say hello
. You need references or pointers for polymorphism, not necessarily dynamic allocation!
Having said this, when you want a container of derived classes, then std::vector<Father>
won't do. Public inheritance models a "is-a" relation, so a Son
is a Father
, but a Father
is not a Son
(notice how wrong and misleading the father-son analogy is?!?). Hence when you put a Son
into a vector of Father
s, then the object gets sliced and only the Father
part is stored in the vector (read about "object slicing").
Moreover, auto p= &Son();
is wrong, because the created object is temporary and its life time ends at the end of that line. The pointer you store in the vector is dangling (it points to an object whose lifetime already ended).
To store pointers in the container you can use dynamic allocations. For example, with std::unique_ptr
s:
int main()
{
std::vector<std::unique_ptr<Father>> v;
for(int i(0);i<5;i++){
v.emplace_back(new Son);
}
for(auto& p:v){
p->Say();
}
}
Note that you have to use auto&
for the range based for loop, because unique_ptr
doesn't copy. unique_ptr
does the dirty work: the objects will get deleted automatically when the unique_ptr
s get destroyed (which is when the vector goes out of scope).
Upvotes: 2
Reputation: 7034
There are lots of things you are doing wrong. First here is how you can do it right:
int main()
{
Father father1;
Son son1;
Father father2;
Son son2;
std::vector<Father*> v;
v.emplace_back(&father1);
v.emplace_back(&son1);
v.emplace_back(&father2);
v.emplace_back(&son2);
for (auto p : v) {
p->Say();
}
}
Basically you need to allocate the objects on the stack so that the objects will be available for as long as the vector is.
Now what you did is undefined behavior, because you basically had pointers (in the vector) to objects that were already de-allocated (even if you fix what was already said in the comments).
for(int i(0);i<5;i++){
Son s;
v.emplace_back(&s);
// s lifetime end here, but the vector still has pointers to objects that are de-allocated
}
For this bit: v.emplace_back(p);---------------now "o" is cast to Father type
.
I think you tried something completely different: a vector of Father objects std::vector<Father>
and if you try to add Son elements into that you get object slicing // I just added this bit so that you can look it up, this is not the main point here
Upvotes: 0
Reputation: 10982
This is a recurring problem/dilemma: you can maintain a value semantic at the expense of some boilerplate code. Here is a minimal working example of such kind of idea:
#include <iostream>
#include <memory>
#include <vector>
class Father
{
protected:
struct Father_Interface
{
virtual void
Say() const
{
std::cout << "Father say hello" << std::endl;
}
};
using pimpl_type = std::shared_ptr<const Father_Interface>;
pimpl_type _pimpl;
Father(const Father_Interface* p) : _pimpl(p) {}
public:
Father() : Father{new Father_Interface{}} {}
void Say() const { _pimpl->Say(); }
};
class Son : public Father
{
protected:
class Son_Interface : public Father_Interface
{
void
Say() const override
{
std::cout << "Son say hello" << std::endl;
}
};
public:
Son() : Father{new Son_Interface{}} {}
Son& operator=(const Father&) = delete; // fight against object slicing
};
int
main()
{
std::vector<Father> v;
v.emplace_back(Father());
v.emplace_back(Son());
v.emplace_back(Father());
for (const auto& v_i : v)
{
v_i.Say();
}
}
which prints:
Father say hello Son say hello Father say hello
You can also read about:
Upvotes: 1