C++ композиция с абстрактным классом

Допустим, у меня есть абстрактный класс, который дорого создавать и копировать:

class AbstractBase {
public:
    AbstractBase() {
        for (int i = 0; i < 50000000; ++i) {
            values.push_back(i);
        }
    }

    virtual void doThing() = 0;

private:
    vector<int> values;
};

Он имеет два подкласса FirstDerived:

class FirstDerived : public AbstractBase {
public:
    void doThing() {
        std::cout << "I did the thing in FirstDerived!\n";
    }

};

и SecondDerived:

class SecondDerived : public AbstractBase {
public:
    void doThing() {
        std::cout << "I did the thing in SecondDerived!\n";
    }
};

Кроме того, я хотел бы создать класс, который использует FirstDerived или SecondDerived, используя композицию (а не агрегацию). Это означает, что я хочу, чтобы ComposedOfAbstractBase владел любым переданным временным файлом. Если бы я не использовал абстрактные классы, этот класс выглядел бы так: (в С++ 11)

class ComposedOfWhicheverDerived {
public:
    ComposedOfWhicheverDerived(AbstractBase abstract_base) : abstract_base(std::move(abstract_base)) {;}
private:
    AbstractBase abstract_base;
};

Однако это не работает с абстрактными классами, потому что я никогда не смогу создать экземпляр AbstractBase, даже если буду остерегаться передачи временного AbstractBase, например:

ComposedOfWhicheverDerived a(FirstDerived());

Для компилятора это так же плохо, как:

ComposedOfWhicheverDerived b(AbstractBase());

Потому что у меня все еще есть экземпляр AbstractBase в объявлении класса.

Следующее решение, которое я придумал, это:

class ComposedOfAbstractBase {
public:
    ComposedOfAbstractBase(AbstractBase&& abstract_base) : some_derived_instance(abstract_base) {;}

private:
    AbstractBase& some_derived_instance;
};

Это работает отлично (хотя я не совсем понимаю)! Оба эти экземпляра действительны и работают по назначению:

ComposedOfAbstractBase a(FirstDerived());
ComposedOfAbstractBase b(SecondDerived());

Он не создает копию любого переданного временного AbstractBase, и разрешено хранить ссылку на AbstractBase. Хотя в лучшем случае ссылка на ссылку rvalue кажется неясной: она не означает, что ComposedOfAbstractBase владеет каким бы временным параметром ни было передано. Кроме того, оказывается, что это решение кажется неоптимальным. Чтобы показать это, я создал этот класс:

class ComposedOfFirstDerived {
public:
    ComposedOfFirstDerived(FirstDerived first_derived) : first_derived(std::move(first_derived)) {;}

private:
    FirstDerived first_derived;
};

Который может принимать только FirstDerived, поэтому мы можем применить std::move, чтобы разгрузить временное владение. Я могу сделать такой экземпляр:

ComposedOfFirstDerived c(FirstDerived()); 

Интересно, что этот класс постоянно создается на 10% быстрее, чем ComposedOfAbstractClass.

Кто-нибудь знает, что здесь происходит? Почему ComposedOfFirstDerived создается намного быстрее, чем ComposedOfAbstractBase? Есть ли лучший способ сделать композицию с абстрактными классами, или я застрял с неоптимальным решением?

Извините, если это был вопрос с полным ртом. Я ценю любого, кто найдет время, чтобы прочитать его и дать искренний ответ, потому что я вне тупика!


person donutmonger    schedule 28.08.2015    source источник
comment
неоптимальное решение совершенно неверно. Он не владеет временными.   -  person Piotr Skotnicki    schedule 28.08.2015
comment
Что же тогда владеет временными? Делает ли это эквивалент создания нового указателя, а затем никогда не удаляет его?   -  person donutmonger    schedule 28.08.2015
comment
Нет, это эквивалентно удержанию указателя на недействительную область памяти.   -  person Piotr Skotnicki    schedule 28.08.2015


Ответы (1)


ComposedOfAbstractBase не является решением. Вы держите болтающуюся ссылку.

Поскольку AbstractBase, как следует из названия, является абстрактным, вы не можете хранить его по значению. Вы можете удерживать только один по ссылке или указателю. Поскольку ссылка не может владеть объектом, остается указатель. А современный способ владения указателем — использовать unique_ptr:

class ComposedOfAbstractBasePtr {
public:
    ComposedOfAbstractBasePtr(std::unique_ptr<AbstractBase> p)
    : some_derived_instance(std::move(p))
    { }

private:
    std::unique_ptr<AbstractBase> some_derived_instance;
};

Обратите внимание, что ваш AbstractBase не имеет виртуального деструктора. Вы должны исправить это.

person Barry    schedule 28.08.2015
comment
Как тогда мне это инициализировать? Я больше не могу делать ComposedOfAbstractBasePtr(FirstDerived()). Должен ли я создавать unique_ptr для объекта, а затем передавать его? Что мешает мне удалить этот ptr позже из-за невнимательности? - person donutmonger; 28.08.2015
comment
@donutmonger Ты бы сделал make_unique<FirstDerived>(). Или вы можете сделать unique_ptr ранее и move в нем. Пока вы имеете дело исключительно с unique_ptr, вы не можете небрежно удалить его. Вам нужно будет явно ввести delete p.get();, что должно быть красным флажком... - person Barry; 28.08.2015
comment
@donutmonger: ComposedOfAbstractBasePtr(std::make_unique<FirstDerived>()) Вы не можете применить delete к unique_ptr, и даже если бы вы могли, вы все равно передаете безымянный временный объект. Это не может пойти не так из-за невнимательности. - person Christian Hackl; 28.08.2015
comment
Большое спасибо, теперь это имеет гораздо больше смысла! - person donutmonger; 28.08.2015