C++11 gaves us the ability to create anonymous unions with non-trivial members. That can be very useful sometimes - for example, if I want to create Holder class for some non-trivial object without default ctor.
Let's make this NonTrivial object more interesting by giving it a virtual method:
#include <stdint.h>
#include <stdio.h>
struct Base
{
virtual void something() { printf("something\n"); }
};
struct NonTrivial : Base
{
explicit NonTrivial( int ) : a(1), b(2), c(3), d(4) { printf("NonTrivial\n"); }
virtual void something() override { printf("something non trivial\n"); }
int a;
int b;
int c;
int d;
};
struct Holder
{
Holder() : isNonTrivial(false), dummy(0x77) {}
Holder( NonTrivial n) : isNonTrivial(true), nonTrivial( n ) {}
bool isNonTrivial;
union
{
int dummy;
NonTrivial nonTrivial;
};
Holder & operator=( const Holder & rhs )
{
isNonTrivial = rhs.isNonTrivial;
if( isNonTrivial )
nonTrivial = rhs.nonTrivial;
return *this;
}
};
int main() {
Holder holder_1;
NonTrivial n(1);
Holder holder_2( n );
holder_1 = holder_2;
holder_2.nonTrivial.something();
holder_1.nonTrivial.something();
return 0;
}
This just works. However, this works only because compiler doesn't actually make a virtual call here. Let's force it:
Base * ptr = &holder_1.nonTrivial;
ptr->something();
This produces a segfault.
But why? I did more or less the obvious thing - checked if holder holds a non-trivial object and if so - copied it.
After reading the assembly I saw that this operator=
doesn't actually copy vtable pointer from rhs.nonTrivial. I assume that this happens because operator=
for NonTrivial should only be called on fully-constructed object and fully-constructed object should already have its vtable pointer initialized - so why bother and copy it?
Questions:
operator=
look like to
create a full copy of nonTrivial object? I have two ideas - delete
operator=
entirely and force the user to use copy ctor - or use
placement new instead of nonTrivial = rhs.nonTrivial
- but maybe
there are some other options?P.S. I'm aware of std::optional and the like, I'm trying to understand how to do it myself.
If anybody will stumble upon this question looking for a quick answer, here's how I solved this issue using placement new:
template< typename T, typename ... Args >
void inplace_new( T & obj, Args && ... args )
{
auto * t = &obj;
t = new(t) T{ args... };
}
Holder & operator=( const Holder & rhs )
{
isNonTrivial = rhs.isNonTrivial;
if( isNonTrivial )
inplace_new( nonTrivial, rhs.nonTrivial );
return *this;
}
Don't forget #include <new>
:)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With