Reputation: 57
#include <iostream>
class derive1{
public:
derive1() = default;
~derive1() = default;
virtual void func() { std::cout << "derive 1" << std::endl; }
};
class derive2 {
public:
derive2() = default;
~derive2() = default;
virtual void func() { std::cout << "derice 2" << std::endl; }
};
union classUnion {
classUnion() {};
~classUnion() {};
derive1 obj1;
derive2 obj2;
};
int main() {
classUnion u1;
u1.obj1.func(); // <-- OK print 'derive 1'
derive1 &dev1 = u1.obj1;
dev1.func(); // <-- OK print 'derive 1'
derive1 *ptr = &(u1.obj1);
ptr->func(); // <-- core dump/seg fault
return 0;
}
I thought C++11 allow non-trivial constructor (with virtual function). I can't see what's the problem here. I use "g++ -std=c+11 test.cpp" to compile it (gcc 4.8 and gcc 5.0).
Upvotes: 3
Views: 576
Reputation: 17714
The problem is that you never initialize the object inside the union. At least, the easiest way to make it work is the following little tweak:
union classUnion {
classUnion() {};
~classUnion() {};
derive1 obj1={}; // unions can have one inline initializer
derive2 obj2;
};
However, if you instead do this:
int main() {
classUnion u1;
u1.obj1 = derive1{};
...
}
It still will crash. The reason why is because you are assigning into an uninitialized object, and in particular you have a user defined destructor (i.e. a virtual one).
Consider the following: (http://en.cppreference.com/w/cpp/language/union.)
If members of a union are classes with user-defined constructors and destructors, to switch the active member, explicit destructor and placement new are generally needed:
So to realistically use classes with virtual functions (which typically need virtual destructors), you're going to need to use placement new and manual destruction calls, like so:
int main() {
classUnion u1;
new (&u1.obj1) derive1{};
... // use obj1
u1.obj1.~derive1();
new (&u1.obj2) derive2{};
... // use obj2
u1.obj2.~derive2();
}
Upvotes: 5
Reputation: 109219
None of the calls to func()
in your example are OK, they're all undefined behavior. A union
does not default initialize any of its members; if it were to do so, which one would it initialize?
To demonstrate this, add a non-static data member to derive1
and print it within func()
, you'll either see garbage values or your program will crash earlier.
class derive1{
public:
derive1() = default;
~derive1() = default;
virtual void func() { std::cout << "derive 1 " << i << std::endl; }
int i = 20;
};
To fix your example, either change the union
constructor to construct obj1
in the mem-initializer-list
classUnion() : obj1() {};
or add a brace-or-equal initializer for obj1
derive1 obj1 = {};
As for why the first 2 calls to func()
in your example seemed to work, I'm guessing gcc inlined those function calls, but it didn't do so when dealing with the derived1 *
, which caused the last call to fail.
Upvotes: 2