2016-06-15 4 views
2

Я работаю с написанием большой пятерки (конструктор копирования, оператор присваивания копии, конструктор перемещения, оператор назначения перемещения, деструктор). И я немного ударил с синтаксисом конструктора копий.Копировать конструктор с синтаксисом перегрузки назначения?

Скажем, у меня есть класс Foo, который имеет следующие закрытые члены:

template<class data> // edit 
    class foo{ 

    private: 
     int size, cursor; // Size is my array size, and cursor is the index I am currently pointing at 
     data * dataArray; // edit 
    } 

Если бы я был написать конструктор для этого некоторого произвольного размера X это будет выглядеть следующим образом.

template<class data> // edit 
foo<data>::foo(int X){ 
    size = X; 
    dataArray = new data[size]; 
    cursor = 0; // points to the first value 
} 

Теперь, если я хотел сделать конструктор копирования другого объекта называется bar я бы нужно сделать следующее:

template<class data> // edit 
foo<data>::foo(foo &bar){ 
foo = bar; // is this correct? 
} 

Предполагая, что я перегруженный = из приведенного ниже кода:

template<class data> // edit 
    foo<data>::operator=(foo &someObject){ 
     if(this != someObject){ 
      size = someObject.size; 
      cursor = someObject.cursor; 
      delete[] dataArray; 
      dataArray = new data[size]; 
      for(cursor = 0; cursor<size-1;cursor++) 
       dataArray[cursor] = someObject.dataArray[cursor]; 
      } 

     else 
      // does nothing because it is assigned to itself 
     return *this; 
     } 

Является ли мой конструктор копий правильным? Или должен foo = bar вместо этого *this = bar?

Я по-прежнему новичок в шаблонных конструкторах, поэтому, если я сделал какие-либо ошибки в коде, сообщите мне, я исправлю его.

EDIT 1: Благодаря ответом, ниже, Marcin я сделал некоторые изменения в код выше, чтобы сделать его более синтаксически правильно и комментировал их с //edit они суммированы в приведенном ниже списке:

  1. ранее template<classname data>, что неверно, должно быть template <typename data> или template <class data> для функций и классов соответственно.
  2. ранее int*dataArray; это missuses шаблон и должен быть data* dataArray;
+4

** 1. ** Напишите конструктор копирования для инициализации каждого члена из r.h.s. объект (как ваш текущий 'op =' делает). ** 2. ** Напишите свое копирование в терминах конструктора копирования. См. [Копия и своп-идиома] (https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Copy-and-swap) (также [здесь] (http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom)) для деталей. Причина, по которой вы должны сделать это таким образом, состоит в том, чтобы избежать инициализации по умолчанию, а затем назначать при написании конструктора копирования, что делает ваша текущая реализация. – Andrew

+1

Также смотрите: http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom – NathanOliver

+3

Это '* this = bar', и конструктор копирования должен взять исходный код' const' Справка. Кроме того, оператор присваивания проще реализовать и меньше подвержен ошибкам с использованием [идиомы копирования и свопинга] (http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom?rq=1). Но в этом случае вы должны просто заменить 'dataArray' и' size' на 'std :: vector' и оставить все значения по умолчанию для copy/move constructor и destructor; оператор присваивания также становится тривиальным. –

ответ

1

Ваш класс Foo не внутренне использовать data параметр шаблона. Я полагаю, вы хотите использовать его здесь:

int * dataArray; // should be: data * dataArray; 

Вы также не разрешается использовать classname ключевое слово, но typename или class. У вас также много других ошибок компиляции в вашем коде.

Ваш конструктор копирования не так, он не будет компилироваться:

foo = bar; // is this correct? - answer is NO 

foo это имя класса в этом контексте, поэтому ваше предположение верно. *this = someObject это будет работать (с дополнительными исправлениями, по крайней мере dataArray должен быть установлен в nullptr), но ваши переменные класса будут по умолчанию сконструированы сначала с помощью конструктора копирования только для того, чтобы быть перезаписаны оператором присваивания, поэтому его тихая неэффективность.Для более читать здесь:

Calling assignment operator in copy constructor

Is it bad form to call the default assignment operator from the copy constructor?

+0

Установите значение 'nullptr'? Означает ли это, что мой массив должен быть инициализирован в конструкторе? И ваш ответ означает, что 'operator =() {}' настроен нормально? Я знаю, что это может быть немного хлопотно, но можете ли вы показать мне другие ошибки компиляции? Я изменил код, основанный на нескольких вещах, которые вы указали. Пожалуйста, посмотрите. – Callat

+1

@Hikari, если вы не задаете dataArray для nullptr перед * this = bar; то operator = будет вызывать delete [] в dataArray с неопределенным значением. Чтобы увидеть другие места с ошибками компиляции, сравните свой код с этим: http://coliru.stacked-crooked.com/a/6f78eedbee39cf6a – marcinj

2

Лучший способ добиться того, что вы хотите использовать класс, который уже обрабатывает задание, копирование и перемещение, заботясь о своем управлении памятью для вас. std::vector делает именно это и может непосредственно заменить ваш динамически выделенный массив и размер. Классы, которые это делают, часто называются классами RAII.


Сказав это, и при условии, что это упражнение правильно реализации различных специальных функций-членов, я хотел бы предложить, что вы продолжите через copy and swap idiom. (См. What is the copy and swap idiom? о SO, для получения более подробной информации и комментариев). Идея состоит в том, чтобы определить операцию присваивания в терминах конструктора копирования.

Начинать с членов, конструктора и деструктора. Они определяют собственности семантику членов вашего класса:

template <class data> 
class foo { 
public: 
    foo(const size_t n); 
    ~foo(); 

private: 
    size_t size; // array size 
    size_t cursor; // current index 
    data* dataArray; // dynamically allocated array 
}; 

template <class data> 
foo<data>::foo(const size_t n) 
: size(n), cursor(0), dataArray(new data[n]) 
{} 

template <class data> 
foo<data>::~foo() { 
    delete[] dataArray; 
} 

Здесь выделяется память в конструкторе и высвобождены в деструкторе. Затем напишите конструктор копирования.

template <class data> 
foo<data>::foo(const foo<data>& other) 
: size(other.size), cursor(other.cursor), dataArray(new data[other.size]) { 
    std::copy(other.dataArray, other.dataArray + size, dataArray); 
} 

(вместе с декларацией, foo(const foo& other); внутри тела класса). Обратите внимание, как это использует списки инициаторов членов, чтобы установить переменные-члены в значения в объекте other. Выполняется новое распределение, а затем в теле конструктора копирования вы копируете данные из объекта other в этот объект.

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

template <class data> 
foo<data>& foo<data>::operator=(const foo<data>& rhs) { 
    foo tmp(rhs); // Invoke copy constructor to create temporary foo 

    // Swap our contents with the contents of the temporary foo: 
    using std::swap; 
    swap(size, tmp.size); 
    swap(cursor, tmp.cursor); 
    swap(dataArray, tmp.dataArray); 

    return *this; 
} 

(вместе с декларацией в классе, foo& operator=(const foo& rhs);).

[- Помимо этого: вы можете не записывать первую строку (явно копируя объект), приняв аргумент функции значением. Это то же самое, и может быть более эффективным в некоторых случаях:

template <class data> 
foo<data>& foo<data>::operator=(foo<data> rhs) // Note pass by value! 
{ 
    // Swap our contents with the contents of the temporary foo: 
    using std::swap; 
    swap(size, rhs.size); 
    swap(cursor, rhs.cursor); 
    swap(dataArray, rhs.dataArray); 

    return *this; 
} 

Однако, это может вызвать неоднозначные перегрузки, если вы также определить оператор присваивания шаг. -]

Первое, что нужно сделать, это создать копию объекта, из которого он назначен. Это использует конструктор копирования, поэтому детали того, как копируется объект, нужно реализовать только один раз, в конструкторе копирования.

После того, как копия была сделана, мы заменим наши внутренние детали внутренними элементами копии. В конце тела функции копия tmp выходит за пределы области видимости, а ее деструктор очищает память. Но это не память, которая была выделена в начале функции; это память, которую наш объект использовал, прежде чем мы заменили наше состояние на временное.

Таким образом, детали распределения, копирования и освобождения сохраняются там, где они принадлежат, в конструкторах и деструкторе. Оператор присваивания просто копии и свопы.

Это имеет дополнительное преимущество, более простое и простое: это безопасное исключение. В приведенном выше коде ошибка выделения может привести к тому, что исключение будет создано при создании временного. Но мы еще не изменили состояние нашего класса, поэтому наше состояние остается согласованным (и правильным) даже в случае неудачи назначения.

Следуя той же логике, операции перемещения становятся тривиальными. Конструктор перемещения должен быть определен, чтобы просто взять собственность на ресурс и оставить источник (перемещенный объект) в четко определенном состоянии. Это означает, что элемент источника находится в nullptr, так что последующий delete[] в своем деструкторе не вызывает проблем.

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

Полный, компилируемый и выполняемый пример можно увидеть here.

+0

Благодарим вас за фантастический ответ. Я все еще не совсем получаю RAII, но я думаю, что с практикой в ​​более мелких тестовых случаях я смогу это повесить. Я действительно многому научился из вашего ответа, но с точки зрения простоты я нашел тот, который ниже был проще для реализации проблемы, с которой я столкнулся. Поэтому я принял это. – Callat