Правильный способ поместить объект emplace_back в std::list

У меня есть класс с множеством членов (в основном int, float и некоторые динамические контейнеры). У меня есть std::list myclassobjects. В конструкторе этого класса после установки всех параметров вызываю

myclassobjects.emplace_back(*this)

Это работает и не вызывает проблем с распределением; У меня никогда не было проблем с sigsegv или подобным. Однако я не совсем уверен, что это правильно или что это не вызывает медленной утечки памяти.

Безопасен ли этот метод или он вызывает утечку памяти? Если да, то каков «правильный» способ создания объекта и немедленного помещения его в std::list?


person nathanburns    schedule 28.09.2017    source источник
comment
Вы вставляете копию *this. Я думаю, ты этого не хочешь.   -  person StoryTeller - Unslander Monica    schedule 28.09.2017
comment
То, что вы делаете, ничем не отличается от myclassobjects.push_back(*this). т.е. он создает копию текущего объекта и добавляет ее в конец списка.   -  person Some programmer dude    schedule 28.09.2017
comment
как правило, если вам нужно сохранить набор всех созданных объектов, вы должны хранить указатели на них или... у вас будет фабрика/синглтон, который будет создавать и хранить их и возвращать указатель или ссылка на созданный объект   -  person kmdreko    schedule 28.09.2017


Ответы (2)


Прежде всего, emplace_back создает объект на месте, пересылая его аргументы. Итак, в конечном итоге вы вызываете конструктор копирования вашего класса с аргументом *this.

Я пытаюсь придумать случай, когда необходимо добавить объекты в список внутри конструктора, и мне трудно. Я думаю, что есть лучшие альтернативы.

Поместите его в список вне конструктора

Просто создайте его следующим образом.

myclassobjects.emplace_back(/*constructor params*/);

В С++ 17 это даже возвращает ссылку на вновь созданный объект.

MyClass& myclass_ref = myclassobjects.emplace_back(/*constructor params*/);
do_something_with_my_class_object(myclass_ref);

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

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

Если вам абсолютно необходимо иметь каждый объект в списке и вы не хотите, чтобы он был копией, используйте статический фабричный метод. Если список и вызываемый объект должны совместно владеть, вы можете использовать список std::shared_ptr и сделать следующее.

MyClass {
private:
    MyClass();    // private constructor forbids on the stack variables

public:
    static std::shared_ptr<MyClass> create_instance() {
        auto ptr = make_shared<MyClass>();    // We can access the constructor here
        myclass_objects.emplace_back(ptr);
        return ptr;
    }
}

С другой стороны, если ваш список гарантированно переживет обработчик вызываемого объекта, более целесообразно иметь список std::unique_ptr, которые содержат объекты и возвращают ссылки на них.

MyClass {
private:
    MyClass();    // private constructor forbids on the stack variables

public:
    // pre C++17
    static MyClass& create_instance() {
        auto ptr = make_unique<MyClass>();
        auto& ref = *ptr;    // Store ref before we move pointer
        myclass_objects.emplace_back(std::move(ptr));
        return ref;
    }

    // C++17
    static MyClass& create_instance() {
        auto ptr = make_unique<MyClass>();
        return *(myclass_objects.emplace_back(std::move(ptr)));
    }
}

Конечно, эти примеры охватывают только конструкторы по умолчанию. Поскольку вам обычно приходится работать с большим количеством конструкторов, вам, вероятно, понадобится шаблонный create_instance, который перенаправляет свои аргументы конструктору.

person patatahooligan    schedule 28.09.2017
comment
Почему в первом примере вызова emplace_back() вы создали объект на месте, а не передавали параметры конструктора напрямую? - person Andy; 28.09.2017
comment
Если я не сделаю этого таким образом, я создам локальный объект и отправлю его копию в список. Класс может предложить конструктор перемещения, чтобы сделать вещи дешевле, но я все равно создал лишний объект без всякой причины. С emplace_back список выделяет пространство и создает объект непосредственно в этом пространстве, пересылая аргументы. - person patatahooligan; 28.09.2017
comment
Верно, имеет смысл использовать emplace_back() вместо push_back(), но почему вы назвали emplace_back() именно так, а не так: emplace_back(/* constructor params*/)? - person Andy; 29.09.2017
comment
Извините, опечатка. Это должно было быть то, что ты сказал. - person patatahooligan; 29.09.2017
comment
Хорошо, это имеет смысл. Просто хотел убедиться, что я ничего не пропустил. :) - person Andy; 29.09.2017
comment
Спасибо @patatahooligan, это дало мне достаточно знаний и то, что кажется «правильным» способом сделать это. Я пошел с list.emplace_back (параметры конструктора). - person nathanburns; 04.10.2017

Невозможно хранить ваш объект в std::list или любом другом контейнере. Объект — это область хранения согласно стандартному определению объекта. Регион хранения не может быть сохранен, он просто есть.

Предположим, что X — существующий объект. Контейнер не может хранить сам X, но значение, каким-то образом связанное с X. Есть два варианта.

  1. Сохраните копию файла X.
  2. Сохраните (интеллектуальный) указатель или ссылочную оболочку на X.

Поскольку вы устанавливаете *this, вы сохраняете копию. Это может быть или не быть тем, что вам нужно.

Чтобы сохранить голый указатель, вам пришлось бы emplace_back(this) и, конечно же, изменить тип списка. Это опасно, потому что когда X перестанет существовать, ваш список все еще может иметь висячий указатель на него. Вы должны убедиться, что этого не произойдет.

Возможно, вы захотите рассмотреть возможность хранения общих указателей, но если вы хотите включить его, унаследовав enable_shared_from_this, имейте в виду, что shared_from_this опасно для использования в конструкторе.

person n. 1.8e9-where's-my-share m.    schedule 28.09.2017