如何使用虚方法为具有非平凡成员的匿名联合编写operator =

Amo*_*mum 9 c++ unions c++11

C++ 11使我们能够创建具有非平凡成员的匿名联合.这有时非常有用 - 例如,如果我想为没有默认ctor的某些非平凡对象创建Holder类.
让我们通过给它一个虚拟方法让这个NonTrivial对象更有趣:

#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;

}
Run Code Online (Sandbox Code Playgroud)

这只是有效的.但是,这只是因为编译器实际上不在此处进行虚拟调用.让我们强迫它:

Base * ptr = &holder_1.nonTrivial;

ptr->something(); 
Run Code Online (Sandbox Code Playgroud)

这会产生段错误.
但为什么?我做了或多或少显而易见的事情 - 检查持有人是否拥有一个非平凡的对象,如果是这样 - 复制它.
在阅读程序集后,我看到这operator=实际上并没有从rhs.nonTrivial复制vtable指针.我假设发生这种情况是因为operator=NonTrivial只应该在完全构造的对象上调用,而完全构造的对象应该已经初始化了它的vtable指针 - 那么为什么要打扰并复制呢?

问题:

  1. 我的想法是否正确?
  2. 应该如何operator=创建nonTrivial对象的完整副本?我有两个想法 - operator=完全删除 并强制用户使用copy ctor - 或使用placement new而不是nonTrivial = rhs.nonTrivial- 但是可能还有其他一些选项?

PS我知道std :: optional之类的东西,我正在努力了解自己该怎么做.

Amo*_*mum 1

如果有人偶然发现这个问题并寻求快速答案,以下是我使用 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;
}
Run Code Online (Sandbox Code Playgroud)

别忘了#include <new>:)