Лучший способ добиться того, что вы хотите использовать класс, который уже обрабатывает задание, копирование и перемещение, заботясь о своем управлении памятью для вас. 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.
** 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
Также смотрите: http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom – NathanOliver
Это '* 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; оператор присваивания также становится тривиальным. –