Списки инициализаторов: конструкторы копирования и операторы присваивания = избыточность?

Кажется, что списки инициализаторов - это хорошая идея для конструкторов ваших классов и, как я предполагаю, также для конструктора копирования. Для оператора присваивания нужно назначить каждый член в теле функции. Рассмотрим следующий простой блок:

class Foo {
private: 
  int a,b;
public:
  Foo(int c, int d)  : a(c), b(d) {}
  Foo(const Foo & X) : a(X.a), b(X.b) {}
  Foo& operator=(const Foo& X) {
    if (this == &X) return *this;
    a = X.a;
    b = X.b;
    return *this;
  }
};

Если класс имеет умеренное количество элементов данных, есть три места, где можно испортить различные назначения / инициализации. Под этим я подразумеваю, что если бы конструктор копирования выглядел так:

  Foo(const Foo & X) : a(X.a), b(X.a) {}

или в операторе = отсутствует строка. Поскольку оператор присваивания и конструктор копирования часто имеют одинаковый эффект (в том смысле, что мы копируем члены из одного Foo в другой), могу ли я «повторно использовать» код из конструктора копирования или оператора присваивания или наоборот?


person Hooked    schedule 08.11.2011    source источник
comment
Получить правильный конструктор копирования и оператор присваивания намного сложнее, чем кажется; вам действительно стоит прочитать FAQ по идиоме копирования и обмена . С другой стороны, как уже было сказано, в этом случае нет необходимости в объявленных пользователем конструкторах копирования и операторах присваивания.   -  person Matteo Italia    schedule 09.11.2011
comment
Я обычно использую оператор = в конструкторе: Foo (const Foo & X) {* this = X;}   -  person Lucian    schedule 09.11.2011
comment
@freerider: в общем, это не очень хорошая идея, если ваш класс управляет ресурсами, которые operator= необходимо освободить перед копированием.   -  person Matteo Italia    schedule 09.11.2011
comment
@ matteo italia: Я знаю, но я только что ответил на вопрос в этом примере, который не содержит выделения кучи. Конечно, не стоит использовать в более сложных классах.   -  person Lucian    schedule 09.11.2011


Ответы (3)


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

Если есть элементы, которые нельзя правильно скопировать, используйте интеллектуальные указатели или другие объекты RAII. Эти объекты должны нуждаться в специальных конструкторах / присваиваниях копий. И они нужны им только для одного своего члена.

Все остальное использовать их не стоит.

person Nicol Bolas    schedule 08.11.2011
comment
конструкторы распространены, чтобы сделать множество вариантов. Я согласен с уступкой. - person Mooing Duck; 09.11.2011
comment
@MooingDuck: Но конструкторы copy можно оставить компилятору для записи (по крайней мере, это должно быть безопасно, если компилятор может безопасно писать оператор присваивания копии). - person CB Bailey; 09.11.2011
comment
... = default; ваш друг; используй это. - person Michael Price; 09.11.2011
comment
@MooingDuck Компилятор, даже VC2010, по возможности создаст для вас конструктор копирования и оператор присваивания копии. Этого не будет только в том случае, если у вас есть члены, у которых нет конструкторов / присваиваний копий. - person Nicol Bolas; 09.11.2011
comment
@MooingDuck: Почему? Просто позвольте сгенерированному компилятору сделать правильные вещи, вам не нужно определять его, даже если вы определяете другие конструкторы. - person CB Bailey; 09.11.2011
comment
@NicolBolas: Я полагал, что создание других конструкторов заставит компилятор не создавать их, но несколько быстрых тестов показывают обратное. Виноват. Придерживайтесь версий, созданных компилятором. - person Mooing Duck; 09.11.2011

Поскольку оператор присваивания и конструктор копирования часто имеют одинаковый эффект.

Вовсе нет, один выполняет инициализацию, а другой - присваивание. Они разные по исходному состоянию объекта, а задачи у них разные (хотя и схожие). Оператор канонического присваивания обычно выполняется как:

Foo& operator=(Foo right) {
    right.swap( *this );
    return *this;
}
person K-ballo    schedule 08.11.2011

Возможно, переадресация всего на оператор присваивания недопустима, но это было обычным явлением в C ++ 03, где это было разрешено.

В C ++ 11 конструкторы проще: перенаправить все конструкторы в один главный конструктор.

class Foo {
private: 
  int a,b;
public:
  Foo(int c, int d)  : a(c), b(d) {}
  Foo(const Foo & X) : Foo(x.a, x.d) {} 
  //syntax may be wrong, I don't have a C++11 compiler
  Foo& operator=(const Foo& X) {
    if (this == &X) return *this;
    a = X.a;
    b = X.b;
    return *this;
  }
}

В C ++ 03 (где это разрешено)

class Foo {
private: 
  int a,b;
  void init(int c, int d) {a=c; b=d;}
public:
  Foo(int c, int d)  : {init(c,d);}
  Foo(const Foo & X) : {init(X.a, X.b);} 
  Foo& operator=(const Foo& X) { init(X.a, X.b);} 
}

Но имейте в виду, что это нельзя использовать в некоторых общих случаях. (любой объект, который нельзя присвоить)

person Mooing Duck    schedule 08.11.2011