Как написать operator = для анонимного объединения с нетривиальными членами с виртуальными методами - PullRequest
0 голосов
/ 15 октября 2018

C ++ 11 дает нам возможность создавать анонимные союзы с нетривиальными членами.Иногда это может быть очень полезно - например, если я хочу создать класс Holder для какого-то нетривиального объекта без ctor по умолчанию.
Давайте сделаем этот нетривиальный объект более интересным, предоставив ему виртуальный метод:

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

}

Это просто работает.Однако, это работает только , потому что компилятор фактически не делает здесь виртуальный вызов.Давайте сделаем это принудительно:

Base * ptr = &holder_1.nonTrivial;

ptr->something(); 

Это приводит к segfault.
Но почему?Я сделал более или менее очевидную вещь - проверил, содержит ли держатель нетривиальный объект, и если так - скопировал его.
После прочтения сборки я увидел, что этот operator= на самом деле не копирует указатель vtable из rhs.nonTrivial.,Я предполагаю, что это происходит потому, что operator= для NonTrivial следует вызывать только для полностью построенного объекта, а полностью построенный объект должен уже иметь инициализированный указатель vtable - так зачем беспокоиться и копировать его?

Вопросы:

  1. Правильно ли мое мышление?
  2. Как должна выглядеть operator= для создания полной копии нетривиального объекта?У меня есть две идеи - полностью удалить operator= и заставить пользователя использовать копию ctor - или использовать новое размещение вместо nonTrivial = rhs.nonTrivial - но, возможно, есть другие варианты?

PS Язнаю о std :: необязательный и тому подобное, я пытаюсь понять, как это сделать сам.

1 Ответ

0 голосов
/ 18 октября 2018

Если кто-нибудь наткнется на этот вопрос в поисках быстрого ответа, вот как я решил эту проблему, используя новое размещение:

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

Не забудьте #include <new>:)

...