Как скопировать производный класс с помощью только ссылки на базовый класс - PullRequest
2 голосов
/ 07 января 2020

Я пытаюсь сохранить ссылки на объекты, которые наследуются от вложенного абстрактного базового класса, внутри std::set во внешнем классе.

Позвольте мне сначала показать вам код, так как я думаю, что это намного яснее :

Интерфейс Class.h:

#ifndef Class_H_
#define Class_H_

#include <set>
#include <memory>

class Class
{
    public:
        class AbstractBase;
    private:
        std::set< std::shared_ptr<AbstractBase>> set;
    public:
        Class();
        ~Class();

        void add(AbstractBase& object);
};

#endif

Интерфейс Abstract.h:

#ifndef ABSTRACT_H_
#define ABSTRACT_H_

#include "Class.h"

class Class::AbstractBase
{
    friend class Class;

    public:
        virtual ~AbstractBase();
    private:
        virtual void foo(int in) = 0;
};

#endif

Интерфейс Derived.h:

#ifndef DERIVED_H_
#define DERIVED_H_

#include "Class.h"
#include "AbstractBase.h"

class Derived : private Class::AbstractBase
{
    public:
        ~Derived() override;
    private:
        void foo(int in) override;
};

#endif

add . cc реализация:

#include "Class.h"
#include "AbstractBase.h"

void Class::add(AbstractBase& object)
{
    // create a shared pointer to object
    // and insert it in the set
    set.insert(std::make_shared<AbstractBase>(object));  // <-- this is where it fails
}

Таким образом, я хотел бы иметь несколько различных производных объектов, все наследуемые от AbstractBase, которые должны храниться вместе в контейнере std::set. Компиляция не удалась из-за чисто виртуальной функции. Сначала я не использовал std::shared_ptr и думал, что это является причиной сбоя, я нашел этот SO ответ, предлагающий использовать std::shared_ptr вместо этого. Я все еще получаю ошибку компиляции в Class::add, потому что AbstractBase::foo чисто, но я думал, что std::shared_ptr решит эту проблему?

Я нашел похожие вопросы, но ничего, что не помогло мне решить мои специфические c проблема. Может ли кто-нибудь объяснить мне, что я делаю не так?

Спасибо.

РЕДАКТИРОВАТЬ: Wow! Спасибо за информативные ответы, мне потребуется некоторое время, чтобы полностью понять их все и посмотреть, что лучше для меня. Я обновлю этот вопрос, как только закончу!

Ответы [ 3 ]

3 голосов
/ 07 января 2020

Ваша функция пытается сделать копию объекта, выделить общий экземпляр для копии и сохранить указатель на копию.

Поскольку вы намереваетесь «хранить ссылки» в наборе вы, вероятно, намереваетесь хранить объекты в другом месте и на самом деле не хотите копировать. Если эти упомянутые объекты являются общими, то правильное решение состоит в том, чтобы передать общий указатель:

void Class::add(std::shared_ptr<AbstractBase> ptr)
{
    set.insert(std::move(ptr));
}

Если указанные объекты не являются общими, то вы не можете ссылаться на них с помощью общего указателя. Вместо этого вы можете использовать не владеющий указатель:

std::set<AbstractBase*> set;

void Class::add(AbstractBase* ptr);

Однако, будьте очень осторожны с подходом, не имеющим владельца, чтобы поддерживать упомянутые объекты живыми, по крайней мере, до тех пор, пока они упоминаются набором. Это нетривиально. Ссылка может использоваться в качестве аргумента для add, но я рекомендую против этого, так как для вызывающей стороны может быть не очевидно, что функция будет хранить указатель на аргумент дольше, чем выполнение функции.


Если вы хотите скопировать, вы можете использовать виртуальную функцию, которая возвращает общий указатель. Пример:

class Class::AbstractBase
{
    public:
        virtual std::shared_ptr<AbstractBase> copy() = 0;
// ...

class Derived : private Class::AbstractBase
{
    public:
        std::shared_ptr<AbstractBase> copy() override {
            auto ptr = std::make_shared<Derived>(*this);
            return {ptr, static_cast<Class::AbstractBase*>(ptr.get())};
        }
// ...

void Class::add(AbstractBase& object)
{
    set.insert(object.copy());

Чтобы избежать повторения идентичного copy в нескольких производных типах, вы можете использовать CRTP.

2 голосов
/ 07 января 2020

Необходимо уточнить право собственности на объекты, хранящиеся в наборе. Если вы используете std::shared_ptr, вы по сути кодируете, что set внутри каждого Class владеет содержащимися экземплярами. Это несовместимо с методом add(AbstractBase&) - вы не можете получить право собственности на произвольный объект по ссылке. Что если этот объект уже управляется другим shared_ptr?

Может быть, вы на самом деле хотите хранить только копии в наборе. В этом случае см. Другие ответы о способах полиморфного копирования («клонирования») объектов.

Также открыто, почему вы хотите использовать std::set. std::set устанавливает уникальность содержащихся объектов, используя оператор < (или предоставленный пользователем функтор сравнения с эквивалентной семантикой). Вы даже хотите уникальность? Если да, на основании каких критериев? В настоящее время нет способа сравнить сохраненные объекты класса. std::shared_ptr «решает» эту проблему, сравнивая значения внутреннего указателя, но я сомневаюсь, что это то, что вам нужно здесь.

Если вы действительно хотите хранить и сравнивать объекты исключительно на основе их областей памяти и не предполагает владение хранимыми объектами, вы можете просто использовать необработанные указатели. Если вы хотите хранить только целую группу объектов, не заботясь об уникальности (поскольку в настоящее время вы пытаетесь создать копии, каждый сохраненный элемент будет иметь уникальный адрес, и, таким образом, вы в настоящее время никогда не будете использовать этот аспект std::set), возможно, std::vector это лучшее решение. std::set может также помочь определить, эффективно ли присутствует объект в коллекции, но std::vector может сделать то же самое (и, возможно, быстрее, если вы сортируете и выполняете двоичный поиск). Рассмотрите совет в http://lafstern.org/matt/col1.pdf.

2 голосов
/ 07 января 2020

Если вы хотите скопировать класс неизвестного типа Dynami c, есть три известных способа обойти, имея недостаточную информацию :

  1. Есть способ сопоставить объект с обработчиком, ожидающим, что указан c наиболее производный тип. typeid, чаще всего используется функция-член или элемент данных в общем базовом классе, который является его типом * stati c. Это требует времени и является трудоемким в настройке, но, по крайней мере, вам часто не нужно изменять класс или использовать жирные указатели.

  2. Функция для .clone() объекта в статически известной базе. Это называется идиомой виртуального конструктора и, как правило, наиболее эффективно и удобно для установки.

  3. Обведите дополнительный указатель для клонирования. Это наименее инвазивный тип или касающийся дополнительных настроек, но изменяющий интерфейсы.

Что вам больше всего нужно решать.
То есть, если вы действительно хотите скопируйте объект, и вместо него вместо shared_ptr нужно было передать .add().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...