Явный вызов конструктора копирования объекта внутри unique_ptr

Я использую идиому pimpl с const std::unique_ptr для хранения реализации класса. Мой класс должен поддерживать создание копии и назначение копии. Что я хотел бы сделать, так это вручную вызвать конструктор копирования класса impl внутри класса unique_ptr. Однако я не вижу, как это сделать.

#include <memory>

struct potato {
    potato();
    ~potato();
    potato(const potato& p);

private:
    struct impl;
    const std::unique_ptr<impl> _pimpl;
};

struct potato::impl {
    int carbs = 42;
};

potato::potato()
        : _pimpl(std::make_unique<impl>()) {
}

potato::~potato() = default;

potato::potato(const potato& p) {
    // Try to call the copy constructor of impl, stored in unique_ptr, not the
    // unique_ptr copy-constructor (which doesn't exist).
    _pimpl.get()->impl(p._pimpl); // This doesn't work.
}

Я проверил еще один вопрос о явном вызове конструктора копирования для объекта. В одном ответе рекомендуется использовать новое размещение.

Object dstObject;
new(&dstObject) Object(&anotherObject);

Могу ли я использовать это в моем конструкторе копирования? Если да, то как? Я не очень понимаю, что там происходит. Спасибо.


person scx    schedule 07.12.2018    source источник
comment
Вы не можете вызвать конструктор. Конструкторы вызываются автоматически и только при создании нового объекта.   -  person François Andrieux    schedule 08.12.2018
comment
new(&dstObject) Object(&anotherObject); является размещением new и потребует от вас сначала вручную вызвать деструктор dstObject. И тогда, если ваша конструкция не сработает, вы сильно облажались.   -  person François Andrieux    schedule 08.12.2018
comment
У него не может быть конструктора копирования по умолчанию, но, конечно, он может иметь собственный конструктор копирования!   -  person Matthieu Brucher    schedule 08.12.2018
comment
@SombreroChicken, почему ты это говоришь? Это вопрос вызова copy-ctor для содержащегося объекта.   -  person SergeyA    schedule 08.12.2018
comment
При использовании оператора присваивания следует помнить о возможности самоприсваивания. Использование нового размещения, как описал Франсуа, может столкнуться с самокопирующейся конструкцией... эта нетипичная ситуация (если бы она произошла) могла бы привести к некоторой забавной отладке.   -  person Eljay    schedule 08.12.2018
comment
Должен ли unique_ptr быть const?   -  person Galik    schedule 08.12.2018
comment
Небольшой совет: если вы используете идиому pimpl, вы можете свободно «наследовать» семантику движений unique_ptr. Просто удалите const из _pimpl и добавьте в исходный код назначение перемещения и определения ctor по умолчанию.   -  person Julien Villemure-Fréchette    schedule 08.12.2018


Ответы (2)


Что я хотел бы сделать, так это вручную вызвать конструктор копирования класса impl внутри класса unique_ptr.

Здесь кроется ваша ошибка. Поскольку вы находитесь внутри конструктора (копирования) potato, нет уже созданного объекта impl, над которым вам пришлось бы "вручную" вызывать конструктор копирования.

Просто создайте новый impl, передав ему ссылку на исходный impl для копирования.

potato::potato(const potato& p)
    : _pimpl(std::make_unique<impl>(*p._pimpl) {
}

Что касается присваивания, вы можете легко перейти к оператору присваивания impl:

potato &operator=(const potato &p) {
    *_pimpl = *p._pimpl;
    return *this;
}
person Matteo Italia    schedule 07.12.2018
comment
@scx ой, неправильно истолковал вопрос как вопрос о домашнем задании. - person SergeyA; 08.12.2018
comment
@scx: конечно нет. У Assignment в левой руке уже построенный объект, так что это совсем другой зверь. Тем не менее, вам не нужно делать ничего особенного: вам просто нужно перенаправить операцию на базовый _impl, так что просто сделайте что-то вроде *_pimpl = *p._pimpl; return *this;. - person Matteo Italia; 08.12.2018
comment
Опять же, конечно, с классом PIMPL вы всегда можете преобразовать назначение класса-оболочки в уничтожение _pimpl + копирование конструкции нового, но семантика немного отличается (и более расточительна с точки зрения распределения). - person Matteo Italia; 08.12.2018
comment
Я не работаю, так как unique_ptr является константой. Мне нужно придумать что-то еще для задания. - person scx; 08.12.2018
comment
@scx: назначение переадресации работает, constness unique_ptr не влияет на constness возвращаемой ссылки ... он просто блокирует переназначение (и некоторые другие вещи) самого unique_ptr, а не заостренный предмет. - person Matteo Italia; 08.12.2018
comment
@MatteoItalia Спасибо за действительно полный ответ! - person scx; 08.12.2018

Как вы упомянули, вы можете явно вызывать конструкторы в неинициализированном хранилище с помощью оператора размещения new. Вы можете вернуть хранилище объекта в неинициализированное состояние, явно вызвав его деструктор.

Вот простая реализация оператора присваивания, которая явно вызывает конструктор копирования и деструктор, которые вы уже определили как часть вашего интерфейса:

#include <new>

potato& potato::operator=(const potato& x)
{
  if ( this != &x ) { // check for self-assignment!
    this->~potato();
    new(this) potato(x);
  }

  return *this;
}

Вероятно, вы также захотите определить конструктор перемещения и перегрузить оператор присваивания, когда правая часть является временной. То есть перегрузка как для potato&& src, так и для const potato& src. Вы можете использовать идиому подкачки, если ваш класс ее поддерживает, или тот же код, что и выше, но с вызовом new(this) potato(std::move(src));.

Если у вас есть доступ к деструктору и конструктору копирования класса, заключенного в интеллектуальный указатель, вы можете проделать с ними тот же трюк, просто разыменовав интеллектуальные указатели. Хотя вы, наверное, не хотите.

Конструктор копирования по умолчанию и оператор присваивания должны работать нормально, если содержимое класса представляет собой интеллектуальные указатели, контейнеры STL и т. д. Вы, вероятно, захотите скопировать данные, на которые ссылаются интеллектуальные указатели, написав такие вещи, как *p = x или *p = *q или std::swap, а не явные вызовы конструктора копирования.

person Davislor    schedule 07.12.2018