Реализовать copy-ctor с точки зрения оператора копирования или отдельно? - PullRequest
4 голосов
/ 08 ноября 2010

Это не дубликат Реализация конструктора копирования в терминах operator = , но это более конкретный вопрос. (Или так мне нравится думать.)

Введение

Учитывая (гипотетический) класс, подобный этому:

struct FooBar {
  long id;
  double valX;
  double valZ;
  long   valN;
  bool   flag; 
  NonCopyable implementation_detail; // cannot and must not be copied

  // ...
};

мы не можем скопировать это сгенерированными по умолчанию функциями, потому что вы не можете ни копировать конструкцию, ни копировать объект NonCopyable. Однако эта часть объекта является деталью реализации, которую мы на самом деле не заинтересованы в копировании.

Также не имеет никакого смысла писать функцию подкачки для этого , потому что функция подкачки может просто копировать то, что делает std :: swap (за исключением NonCopyable).

Так что, если мы хотим скопировать эти объекты, нам остается реализовать copy-ctor и copy-operator самостоятельно. Это тривиально, просто назначив других членов.

Вопрос

Если нам нужно реализовать ctor и оператор копирования, должны ли мы реализовать ctor копирования в терминах оператора копирования или мы должны «продублировать» код с помощью списка инициализации?

То есть дано:

FooBar& operator=(FooBar const& rhs) {
  // no self assignment check necessary
  id = rhs.id;
  valX = rhs.valX;
  valZ = rhs.valZ;
  valN = rhs.valN;
  flag = rhs.flag;
  // don't copy implementation_detail
  return *this;
}

Должны ли мы написать а)

FooBar(FooBar const& rhs) {
  *this = rhs;
}

или б)

FooBar(FooBar const& rhs)
: id(rhs.id)
, valX(rhs.valX)
, valZ(rhs.valZ)
, valN(rhs.valN)
, flag(rhs.flag)
// don't copy implementation_detail
{ }

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

Ответы [ 5 ]

5 голосов
/ 08 ноября 2010

Обычно вы реализуете оператор присваивания в терминах конструктора копирования (версия @Roger Pate):

FooBar& operator=(FooBar copy) { swap(*this, copy); return *this; }
friend void swap(FooBar &a, FooBar &b) {/*...*/}

Это требует предоставления функции swap, которая меняет местами соответствующие элементы (все, кроме implementation_detail в вашем случае).

Если swap не выбрасывает, этот подход гарантирует, что объект не останется в каком-либо несовместимом состоянии (только с назначенными элементами детали).

Однако в вашем случае, поскольку ни конструктор копирования, ни оператор присваивания не могут генерировать реализующий конструктор копирования в терминах оператора присваивания (a), это также хорошо и более удобно, чем иметь почти идентичный код в обоих местах (b).

1 голос
/ 08 ноября 2010

Хорошо, еще одна попытка, основанная на моем комментарии к этому ответу .

Оберните реализацию_detail в копируемый класс:

class ImplementationDetail
{
public:
  ImplementationDetail() {}
  ImplementationDetail(const ImplementationDetail&) {}
  ImplementationDetail& operator=(const ImplementationDetail&) {}

public: // To make the example short
  Uncopyable implementation_detail;
};

и используйте этот классваш FooBar.Сгенерированный по умолчанию конструктор копирования и оператор назначения копирования для Foobar будут работать правильно.

Возможно, он даже может быть производным от Uncopyable, поэтому вы не получите implementation_detail.implementation_detail во всем коде.Или, если вы управляете кодом в классе creation_detail, просто добавьте пустой конструктор копирования и пустой оператор присваивания.

1 голос
/ 08 ноября 2010

Если вы действительно обеспокоены репликацией std :: swap, почему бы не поместить все, кроме подробностей реализации, в структуру?

struct FooBarCore {
  long id;
  double valX;
  double valZ;
  long   valN;
  bool   flag; 
  // ...
};

struct FooBar {
  FooBarCore core_;
  NonCopyable implementation_detail; // cannot and must not be copied
};

, тогда вы можете использовать std :: swap для этой структуры вВаша функция копирования для FooBar.

FooBar& operator=(const FooBar &src) {
  FooBarCore temp(src.core_)
  swap(temp,*this.core_);
  return *this;
}
1 голос
/ 08 ноября 2010

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

В качестве альтернативы, вы могли бы иметь подструктуру для элементов, не являющихся «деталями реализации», и позволить компилятору сгенерировать код для копирования вдоль строк:

struct X 
{
    struct I
    {
        int x_;
        int y_;
    } i_;
    int z_;

    X() { }

    X(const X& rhs)
      : i_(rhs.i_), z_(0) // implementation not copied
    { }

    X& operator=(const X& rhs)
    {
        i_ = rhs.i_;
        return *this;
    } 
};
0 голосов
/ 08 ноября 2010

Если конструктору копирования не нужно копировать creation_detail и все равно он будет корректным (я сомневаюсь в последнем, но давайте предположим, что это на данный момент), реализация_детали является излишним.

Таким образом, решение, по-видимому, состоит в том, чтобы: сделать статическую реализацию_детали и полагаться на созданный по умолчанию конструктор копирования и оператор присваивания.

...