Конструктор перемещения
Конструктор и оператор перемещения используются компилятором в разных ситуациях:
- конструктор перемещения применяется в местах, где объявление совпадает с определением (инициализацией) rvalue-ссылкой на экземпляр этого же класса, либо посредством direct initialization в конструкторе класса/структуры (если же определение произойдет с помощью lvalue-ссылки, то вызовется конструктор копирования);
- оператор перемещения применяется в местах, где экземпляр класса уже был ранее определен и к нему применяется operator = , который в качестве аргумента приминает rvalue-ссылку на экземпляр этого же класса (если же оператор принимает lvalue-ссылку , то вызовется оператор присваивания).
Про rvalue-ссылки можете почитать здесь, здесь и здесь.
Контрольный пример (для разъяснения отличия в работе данных конструкций)
#include class Buffer < public: Buffer(const std::string& buff) : pBuff(nullptr) , buffSize(buff.length()) < pBuff = new char[buffSize]; memcpy(pBuff, buff.c_str(), buffSize); >~Buffer() < destroy(); >Buffer(const Buffer& other) : pBuff(nullptr) , buffSize(other.buffSize) < pBuff = new char[buffSize]; memcpy(pBuff, other.pBuff, buffSize); >Buffer& operator=(const Buffer& other) < destroy(); buffSize = other.buffSize; pBuff = new char[buffSize]; memcpy(pBuff, other.pBuff, buffSize); return *this; >Buffer(Buffer&& tmp) : pBuff(tmp.pBuff) , buffSize(tmp.buffSize) < tmp.pBuff = nullptr; >Buffer& operator=(Buffer&& tmp) < destroy(); buffSize = tmp.buffSize; pBuff = tmp.pBuff; tmp.pBuff = nullptr; return *this; >private: void destroy() < if (pBuff) delete[] pBuff; >char* pBuff; size_t buffSize; >; Buffer CreateBuffer(const std::string& buff) < Buffer retBuff(buff); return retBuff; >int main() < Buffer buffer1 = CreateBuffer("123"); // срабатывает конструктор перемещения Buffer buffer2 = buffer1; // срабатывает конструктор копирования buffer2 = CreateBuffer("123"); // срабатывает конструктор перемещения, затем оператор перемещения buffer2 = buffer1; // срабатывает оператор присваивания >
Дополнение
В C++11 каждый класс, помимо конструктора по умолчанию, имеет следующие 5 дефолтных операций:
- конструктор копирования (copy constructor);
- оператор присваивания (copy assignment);
- конструктор перемещения (move constructor);
- оператор перемещения (move assignment);
- деструктор (destructor).
При определении одной из этих 5-ти операций рекомендуется явно указать (либо определить, либо объявить с помощью default или delete ) все остальные, т.к. все эти 5 операций тесно связаны. Это будет способствовать лучшему пониманию семантики класса при чтении кода.
Если явно определена одна из упомянутых 5-ти операций (в том числе с использованием default или delete ), то:
- недостающие операции копирования будут определены автоматически с поведением по умолчанию;
- недостающие операции перемещения определены не будут.
Это следует учитывать при написании классов.
Отслеживать
ответ дан 9 фев 2016 в 23:58
StateItPrimitive StateItPrimitive
7,719 1 1 золотой знак 24 24 серебряных знака 47 47 бронзовых знаков
Вопрос фактически звучит как «зачем вообще нужны конструкторы». Вопрос в принципе не относится к «перемещениям», а фактически сводится к принципиальной разнице между конструкторами (копирования, перемещения, и т.д.) и другими функциями-членами класса (операторы присваивания и т.д.)
Конструктор в общем случае работает на «сыром» (несконструированном, непроинициализорованном) блоке памяти. В момент начала работы конструктора объекта как такового еще не существует и он не имеет никакого предсказуемого состояния. Соответственно работа конструктора сводится к созданию/инициализации нового объекта в предоставленном блоке «сырой» памяти. Конструктор копирования, например, копирует это состояние из некоего объекта-образца, конструктор перемещения — перемещает, конструктор преобразования — преобразует и т.д. Конструктор перемещения никоим образом не выделяется из этого ряда.
Оператор присваивания же всегда имеет дело с уже проинициализированным/сконструированным объектом, находящимся в некоем предсказуемом «валидном» состоянии. Работа оператора присваивания сводится к освобождению исходного состояния объекта (освобождению ресурсов, например), за которым следует копирование (или перемещение, или преобразование и т.п.) нового состояния из некоего объекта-источника.
Вот собственно и все. Т.е. операторы присваивания в общем случае делают больше работы, чем конструкторы. Операторы присваивания уничтожают старое состояние объекта и создают новое. А конструкторам уничтожать нечего — они только создают новое состояние.
В рамках этой логики как конструктор перемещения, так и перемещающий оператор присваивания никак из общего ряда не выделяются. Поэтому не ясно, откуда вообще мог возникнуть вопрос вроде «Зачем нужен конструктор перемещения, если есть оператор перемещения?».
Отслеживать
ответ дан 10 фев 2016 в 1:49
AnT stands with Russia AnT stands with Russia
69k 3 3 золотых знака 62 62 серебряных знака 139 139 бронзовых знаков
В принципе, стандартная реализация оператора присваивания могла бы делать следующее: для объекта в левой части оператора присваивания вызвать сначала деструктор, а потом конструктор копирования, передав ему ссылку на объект в правой части. Наверное, это было бы лучше, чем просто копировать все поля из правого объекта в левый, но всё равно это было бы не совсем правильно. Дело в том, что при копировании объекта требуется выделить новый ресурс, но потенциально это может закончиться неудачей. Если это произойдёт, то после неудачной попытки присваивания объект в левой части останется в некорректном состоянии: старый ресурс уже освобождён (вызван деструктор), а новый захватить не получилось. Поэтому при перегрузке оператора присваивания нужно всегда сначала захватывать новый ресурс, и только если эта операция прошла успешно, освобождать старый, заменяя его новым (по этой причине оператор присваивания в принятом ответе написан не совсем правильно). В случае перемещения, в принципе, такой проблемы нет: копируется только ссылка на ресурс, и старый ресурс, которым владел объект в левой части оператора перемещения, может быть освобождён безболезненно, как до копирования ссылки, так и после. Скорее всего, аналогичное копированию разделение на конструктор и оператор в случае перемещения сделано для случая, когда неудачей закончилось освобождение ресурса в левой части оператора перемещения. В этой ситуации необходимо, чтобы объект в правой части продолжал владеть ресурсом. Таким образом сохраняется атомарность копирования и перемещения: либо нам удалось полностью создать копию объекта или полностью переместить ресурс из одного объекта в другой, либо копирование / перемещение прошло неудачно, и ни один из объектов не был изменён.
Отслеживать
ответ дан 18 ноя 2018 в 20:57
8,875 25 25 серебряных знаков 66 66 бронзовых знаков
- c++
- c++11
-
Важное на Мете
Связанные
Похожие
Подписаться на ленту
Лента вопроса
Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.
Дизайн сайта / логотип © 2023 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2023.10.27.43697
Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
M.3 – Конструкторы перемещения и присваивание перемещением
В уроке «M.1 – Введение в умные указатели и семантику перемещения» мы рассмотрели std::auto_ptr , обсудили необходимость семантики перемещения и рассмотрели некоторые недостатки, которые возникают, когда функции, разработанные для семантики копирования (конструкторы копирования и операторы присваивания копированием) переопределяются для реализации семантики перемещения.
В этом уроке мы более подробно рассмотрим, как C++11 решает эти проблемы с помощью конструкторов перемещения и присваивания перемещением.
Конструкторы копирования и присваивание копированием
Во-первых, давайте сделаем обзор семантики копирования.
Конструкторы копирования используются для инициализации класса путем создания копии объекта того же класса. Присваивание копированием используется для копирования одного объекта класса в другой существующий объект класса. По умолчанию, если конструктор копирования и оператор присваивания копированием не указаны явно, C++ предоставляет их. Эти предоставляемые компилятором функции создают поверхностные копии, что может вызывать проблемы для классов, динамически выделяющих память. Таким образом, классы, которые имеют дело с динамической памятью, должны переопределять эти функции для создания глубоких копий.
Возвращаясь к нашему примеру класса умного указателя Auto_ptr из первого урока этой главы, давайте рассмотрим версию, которая реализует конструктор копирования и оператор присваивания копированием, которые делают глубокие копии, и пример программы, которая их проверяет:
template class Auto_ptr3 < T* m_ptr; public: Auto_ptr3(T* ptr = nullptr) :m_ptr(ptr) < >~Auto_ptr3() < delete m_ptr; >// Конструктор копирования // Выполняем глубокое копирование a.m_ptr в m_ptr Auto_ptr3(const Auto_ptr3& a) < m_ptr = new T; *m_ptr = *a.m_ptr; >// Присваивание копированием // Выполняем глубокое копирование a.m_ptr в m_ptr Auto_ptr3& operator=(const Auto_ptr3& a) < // Обнаружение самоприсваивания if (&a == this) return *this; // Освобождаем любые ресурсы, которые уже храним delete m_ptr; // Копируем ресурс m_ptr = new T; *m_ptr = *a.m_ptr; return *this; >T& operator*() const < return *m_ptr; >T* operator->() const < return m_ptr; >bool isNull() const < return m_ptr == nullptr; >>; class Resource < public: Resource() < std::cout ~Resource() < std::cout >; Auto_ptr3 generateResource() < Auto_ptr3res(new Resource); return res; // это возвращаемое значение вызовет конструктор копирования > int main() < Auto_ptr3mainres; mainres = generateResource(); // это присваивание вызовет присваивание копированием return 0; >
В этой программе мы используем функцию с именем generateResource() для создания умного указателя, инкапсулирующего ресурс, который затем передается обратно в функцию main() . Затем функция main() присваивает его существующему объекту Auto_ptr3 .
Когда эта программа запускается, она печатает:
Resource acquired Resource acquired Resource destroyed Resource acquired Resource destroyed Resource destroyed
(Примечание: вы можете получить только 4 сообщения, если ваш компилятор исключает копирование возвращаемого значения из функции generateResource() )
Для такой простой программы происходит слишком много созданий и уничтожений объектов Resource ! Что тут происходит?
Давайте рассмотрим подробнее. В этой программе выполняется 6 ключевых шагов (по одному для каждого напечатанного сообщения):
- Внутри generateResource() создается локальная переменная res , которая инициализируется динамически размещаемым объектом Resource , что приводит к первому сообщению » Resource acquired «.
- res возвращается обратно в main() по значению. Здесь мы возвращаем по значению, потому что res – это локальная переменная, она не может быть возвращена по адресу или по ссылке, потому что будет уничтожена при завершении generateResource() . Таким образом, res копируется конструктором во временный объект. Поскольку наш конструктор копирования выполняет глубокое копирование, здесь выделяется новый Resource , что приводит ко второму сообщению » Resource acquired «.
- res выходит за пределы области видимости, уничтожая первоначально созданный Resource , что приводит к первому сообщению » Resource destroyed «.
- Этот временный объект присваивается mainres путем присваивания копированием. Поскольку наше присваивание копированием также выполняет глубокое копирование, размещается новый Resource , вызывая еще одно сообщение » Resource acquired «.
- Выражение присваивания завершается, и временный объект выходит за пределы области действия выражения и уничтожается, вызывая сообщение » Resource destroyed «.
- В конце main() переменная mainres выходит из области видимости, и отображается наше последнее сообщение » Resource destroyed «.
Короче говоря, поскольку мы вызываем конструктор копирования один раз, чтобы скопировать res во временный объект, и один раз присваивание копированием для копирования временного объекта в mainres , в итоге мы размещаем и уничтожаем в общей сложности 3 отдельных объекта.
Неэффективно, но, по крайней мере, не дает сбоев!
Однако с семантикой перемещения мы можем добиться большего.
Конструкторы перемещения и присваивание перемещением
C++11 определяет две новые функции, обслуживающие семантику перемещения: конструктор перемещения и оператор присваивания перемещением. В то время как цель конструктора копирования и присваивания копированием – выполнить копирование одного объекта в другой, цель конструктора перемещения и присваивания перемещением – передать владение ресурсами от одного объекта к другому (что обычно намного дешевле, чем создание копии).
Определение конструктора перемещения и присваивания перемещением работают аналогично их аналогам для копирования. Однако в то время как копирующие версии этих функций принимают в качестве параметра константную lvalue-ссылку, перемещающие версии этих функций используют в качестве параметра неконстантные rvalue-ссылки.
Вот тот же класс Auto_ptr3 , что и выше, с добавленными конструктором перемещения и оператором присваивания перемещением. Для сравнения мы оставили выполняющие глубокое копирование конструктор копирования и оператор присваивания копированием.
#include template class Auto_ptr4 < T* m_ptr; public: Auto_ptr4(T* ptr = nullptr) :m_ptr(ptr) < >~Auto_ptr4() < delete m_ptr; >// Конструктор копирования // Выполняем глубокое копирование a.m_ptr в m_ptr Auto_ptr4(const Auto_ptr4& a) < m_ptr = new T; *m_ptr = *a.m_ptr; >// Конструктор перемещения // Передача владения a.m_ptr в m_ptr Auto_ptr4(Auto_ptr4&& a) noexcept : m_ptr(a.m_ptr) < a.m_ptr = nullptr; // поговорим об этой строке подробнее ниже >// Присваивание копированием // Выполняем глубокое копирование a.m_ptr в m_pt Auto_ptr4& operator=(const Auto_ptr4& a) < // Обнаружение самоприсваивания if (&a == this) return *this; // Освобождаем любые ресурсы, которые уже храним delete m_ptr; // Копируем ресурс m_ptr = new T; *m_ptr = *a.m_ptr; return *this; >// Присваивание перемещением // Передача владения a.m_ptr в m_ptr Auto_ptr4& operator=(Auto_ptr4&& a) noexcept < // Обнаружение самоприсваивания if (&a == this) return *this; // Освобождаем любые ресурсы, которые уже храним delete m_ptr; // Передаем владение a.m_ptr в m_ptr m_ptr = a.m_ptr; a.m_ptr = nullptr; // поговорим об этой строке подробнее ниже return *this; >T& operator*() const < return *m_ptr; >T* operator->() const < return m_ptr; >bool isNull() const < return m_ptr == nullptr; >>; class Resource < public: Resource() < std::cout ~Resource() < std::cout >; Auto_ptr4 generateResource() < Auto_ptr4res(new Resource); return res; // это возвращаемое значение вызовет конструктор перемещения > int main() < Auto_ptr4mainres; mainres = generateResource(); // это присваивание вызовет присваивание перемещением return 0; >
Конструктор перемещения и оператор присваивания перемещением просты. Вместо того, чтобы выполнять глубокое копирование исходного объект ( а ) в неявный объект this , мы просто перемещаем (крадем) ресурсы исходного объекта. Это включает в себя поверхностное копирование указателя исходного объекта в неявный объект this с последующей установкой для указателя исходного объекта значения nullptr .
При запуске эта программа печатает:
Resource acquired Resource destroyed
Это намного лучше!
Ход программы точно такой же, как и раньше. Однако вместо вызова конструктора копирования и оператора присваивания копированием эта программа вызывает конструктор перемещения и оператор присваивания перемещением. Рассмотрим немного подробнее:
- Внутри generateResource() создается локальная переменная res , которая инициализируется динамически размещаемым объектом Resource , что приводит к первому сообщению » Resource acquired «.
- res возвращается обратно в main() по значению. res перемещается конструктором во временный объект, передавая динамически созданный объект, хранящийся в res , во временный объект. О том, почему это происходит, мы поговорим ниже.
- res выходит из области видимости. Поскольку res больше не управляет указателем (он был перемещен во временный объект), здесь ничего интересного не происходит.
- Временный объект перемещается присваиванием в mainres . Это переносит динамически созданный объект, хранящийся во временном объекте, в mainres .
- Выражение присваивания завершается, временный объект выходит за пределы области действия выражения и уничтожается. Однако, поскольку временный объект больше не управляет указателем (он был перемещен в mainres ), здесь также не происходит ничего интересного.
- В конце main() переменная mainres выходит из области видимости, и отображается наше последнее сообщение » Resource destroyed «.
Поэтому вместо того, чтобы копировать наш объект Resource дважды (один раз для конструктора копирования и один раз для присваивания копированием), мы дважды перемещаем его. Это более эффективно, поскольку объект Resource создается и уничтожается только один раз, а не три раза.
Когда вызываются конструктор перемещения и присваивание перемещением?
Конструктор перемещения и присваивание перемещением вызываются, когда эти функции определены, а аргументом для построения или присваивания является r-значение. Чаще всего это r-значение будет литералом или временным значением.
В большинстве случаев конструктор перемещения и оператор присваивания перемещением не предоставляются по умолчанию, если в классе нет определенных конструкторов копирования, присваивания копированием, присваивания перемещением или деструкторов. Однако дефолтные конструктор перемещения и присваивание перемещением делают то же самое, что и дефолтные конструктор копирования и присваивание копированием (делать копии, а не перемещают).
Правило
Если вам нужен конструктор перемещения и присваивание перемещением, выполняющее перемещения, вам нужно будет написать их самостоятельно.
Ключевой момент в семантике перемещения
Теперь у вас достаточно контекста для понимания ключевой идеи семантики перемещения.
Если мы создаем объект или выполняем присваивание, в котором аргументом является l-значение, единственное разумное, что мы можем сделать, – это скопировать l-значение. Мы не можем предположить, что изменение l-значения безопасно, потому что позже в программе оно может быть снова использовано. Если у нас есть выражение a = b , мы не можем ожидать каких-либо изменений b .
Однако, если мы создаем объект или выполняем присваивание, в котором аргументом является r-значение, тогда мы знаем, что r-значение – это всего лишь временный объект какого-то типа. Вместо того, чтобы копировать его (что может быть дорогостоящим), мы можем просто передать его ресурсы (что дешево) объекту, который мы создаем или которому выполняем присваивание. Это безопасно, потому что временный объект в любом случае будет уничтожен в конце выражения, поэтому мы знаем, что он больше никогда не будет использоваться!
C++11, через rvalue-ссылки, дает нам возможность обеспечивать различное поведение, когда аргументом является r-значение или l-значение, что позволяет нам принимать более разумные и эффективные решения о том, как должны вести себя наши объекты.
Функции перемещения должны всегда оставлять оба объекта в четко определенном состоянии.
В приведенных выше примерах и конструктор перемещения, и функции присваивания перемещением устанавливают a.m_ptr в значение nullptr . Это может показаться лишним – в конце концов, если a – временное r-значение, зачем беспокоиться о выполнении «очистки», если параметр a всё равно будет уничтожен?
Ответ прост: когда a выходит за пределы области видимости, вызывается деструктор a , и a.m_ptr удаляется. Если в этот момент a.m_ptr всё еще указывает на тот же объект, что и m_ptr , тогда m_ptr останется висячим указателем. Когда объект, содержащий m_ptr , в конечном итоге будет использован (или уничтожен), мы получим неопределенное поведение.
Кроме того, в следующем уроке мы увидим случаи, когда a может быть l-значением. В таком случае a не будет уничтожен немедленно, и его можно будет запросить еще до того, как истечет время его жизни.
Автоматические l-значения, возвращаемые по значению, могут быть перемещены вместо копирования
В функции generateResource() в примере выше с Auto_ptr4 , когда переменная res возвращается по значению, она перемещается, а не копируется, даже если res является l-значением. В спецификации C++ есть специальное правило, согласно которому автоматические объекты, возвращаемые функцией по значению, можно перемещать, даже если они являются l-значениями. Это имеет смысл, так как res всё равно будет уничтожен в конце функции! С таким же успехом мы могли бы забрать его ресурсы, вместо того, чтобы выполнять дорогостоящее и ненужное копирование.
Хотя компилятор может перемещать возвращаемые l-значения, в некоторых случаях он может добиться еще большего, просто полностью исключив копирование (что позволяет вовсе избежать необходимости выполнять копирование или перемещение). В таком случае не будут вызываться ни конструктор копирования, ни конструктор перемещения.
Отключение копирования
В приведенном выше классе Auto_ptr 4 мы оставили для сравнения конструктор копирования и оператор присваивания. Но в классах с поддержкой перемещения иногда желательно удалить функции конструктора копирования и присваивания копированием, чтобы гарантировать, что копии не будут созданы. В случае с нашим классом Auto_ptr мы не хотим копировать наш шаблонный объект T – потому что это дорого, и класс T может даже не поддерживать копирование!
Вот версия Auto_ptr , которая поддерживает семантику перемещения, но не поддерживает семантику копирования:
#include template class Auto_ptr5 < T* m_ptr; public: Auto_ptr5(T* ptr = nullptr) :m_ptr(ptr) < >~Auto_ptr5() < delete m_ptr; >// Конструктор копирования - копирование запрещено! Auto_ptr5(const Auto_ptr5& a) = delete; // Конструктор перемещения // Передача владения a.m_ptr в m_ptr Auto_ptr5(Auto_ptr5&& a) noexcept : m_ptr(a.m_ptr) < a.m_ptr = nullptr; >// Присваивание копированием - копирование запрещено! Auto_ptr5& operator=(const Auto_ptr5& a) = delete; // Присваивание перемещением // Передача владения a.m_ptr в m_ptr Auto_ptr5& operator=(Auto_ptr5&& a) noexcept < // Обнаружение самоприсваивания if (&a == this) return *this; // Освобождаем любые ресурсы, которые уже храним delete m_ptr; // Передаем владение a.m_ptr в m_ptr m_ptr = a.m_ptr; a.m_ptr = nullptr; return *this; >T& operator*() const < return *m_ptr; >T* operator->() const < return m_ptr; >bool isNull() const < return m_ptr == nullptr; >>;
Если бы вы попытались передать функции l-значение Auto_ptr5 по значению, компилятор пожаловался бы, что конструктор копирования, необходимый для инициализации аргумента функции, был удален. Это хорошо, потому что мы, вероятно, всё равно должны передавать Auto_ptr5 по константной lvalue-ссылке!
Auto_ptr5 – это (наконец) хороший класс умных указателей. И на самом деле стандартная библиотека содержит класс, очень похожий на этот (и который вы должны использовать вместо этого), с именем std::unique_ptr . Подробнее об std::unique_ptr мы поговорим в этой главе позже.
Еще один пример
Давайте посмотрим на другой класс, который использует динамическую память: простой динамический шаблонный массив. Этот класс содержит конструктор копирования и оператор присваивания копированием, выполняющие глубокое копирование.
#include template class DynamicArray < private: T* m_array; int m_length; public: DynamicArray(int length) : m_array(new T[length]), m_length(length) < >~DynamicArray() < delete[] m_array; >// Конструктор копирования DynamicArray(const DynamicArray &arr) : m_length(arr.m_length) < m_array = new T[m_length]; for (int i = 0; i < m_length; ++i) m_array[i] = arr.m_array[i]; >// Присваивание копированием DynamicArray& operator=(const DynamicArray &arr) < if (&arr == this) return *this; delete[] m_array; m_length = arr.m_length; m_array = new T[m_length]; for (int i = 0; i < m_length; ++i) m_array[i] = arr.m_array[i]; return *this; >int getLength() const < return m_length; >T& operator[](int index) < return m_array[index]; >const T& operator[](int index) const < return m_array[index]; >>;
Теперь давайте, используем этот класс в программе, чтобы показать, как работает этот класс, когда мы размещаем миллион целых чисел в куче. Мы собираемся использовать класс Timer , который мы разработали в уроке «12.18 – Определение времени выполнения кода». Мы будем использовать его, чтобы измерить скорость выполнения нашего кода и показать вам разницу в производительности между копированием и перемещением.
#include #include // для функций std::chrono // Использует показанный выше класс DynamicArray class Timer < private: // Псевдонимы типа, чтобы упростить доступ к вложенному типу using clock_t = std::chrono::high_resolution_clock; using second_t = std::chrono::duration>; std::chrono::time_point m_beg; public: Timer() : m_beg(clock_t::now()) < >void reset() < m_beg = clock_t::now(); >double elapsed() const < return std::chrono::duration_cast(clock_t::now() - m_beg).count(); > >; // Возвращаем копию arr со всеми удвоенными значениями DynamicArray cloneArrayAndDouble(const DynamicArray &arr) < DynamicArraydbl(arr.getLength()); for (int i = 0; i < arr.getLength(); ++i) dbl[i] = arr[i] * 2; return dbl; >int main() < Timer t; DynamicArrayarr(1000000); for (int i = 0; i
На одной из машин автора в режиме релиза эта программа выполнилась за 0,00825559 секунды.
Теперь давайте снова запустим эту же программу, заменив конструктор копирования и присваивание копированием конструктором перемещения и присваиванием перемещением.
#include #include // для функций std::chrono template class DynamicArray < private: T* m_array; int m_length; public: DynamicArray(int length) : m_array(new T[length]), m_length(length) < >~DynamicArray() < delete[] m_array; >// Конструктор копирования DynamicArray(const DynamicArray &arr) = delete; // Присваивание копированием DynamicArray& operator=(const DynamicArray &arr) = delete; // Конструктор перемещения DynamicArray(DynamicArray &&arr) noexcept : m_length(arr.m_length), m_array(arr.m_array) < arr.m_length = 0; arr.m_array = nullptr; >// Присваивание перемещением DynamicArray& operator=(DynamicArray &&arr) noexcept < if (&arr == this) return *this; delete[] m_array; m_length = arr.m_length; m_array = arr.m_array; arr.m_length = 0; arr.m_array = nullptr; return *this; >int getLength() const < return m_length; >T& operator[](int index) < return m_array[index]; >const T& operator[](int index) const < return m_array[index]; >>; class Timer < private: // Псевдонимы типа, чтобы упростить доступ к вложенному типу using clock_t = std::chrono::high_resolution_clock; using second_t = std::chrono::duration>; std::chrono::time_point m_beg; public: Timer() : m_beg(clock_t::now()) < >void reset() < m_beg = clock_t::now(); >double elapsed() const < return std::chrono::duration_cast(clock_t::now() - m_beg).count(); > >; // Возвращаем копию arr со всеми удвоенными значениями DynamicArray cloneArrayAndDouble(const DynamicArray &arr) < DynamicArraydbl(arr.getLength()); for (int i = 0; i < arr.getLength(); ++i) dbl[i] = arr[i] * 2; return dbl; >int main() < Timer t; DynamicArrayarr(1000000); for (int i = 0; i
На той же машине эта программа была выполнена за 0,0056 секунды.
Сравним время выполнения этих двух программ, 0,0056 / 0,00825559 = 67,8%. Версия с перемещением была почти на 33% быстрее!
Явный вызов конструкторов перемещения
Но при этом, если у объекта не задан конструктор перемещения, будет неявно, без уведомлений, вызван конструктор копирования. Как на этапе компиляции проверить, что производится перемещение, а не копирование объекта?
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:
Явный вызов деструктора
Добрый вечер всем! Я раньше читал статью о деструкторах, и там говорилось что можно явно вызвать.
Явный вызов деструктора
Есть класс Person, с полями string,string,long. Исходные данные хранятся в файле. Нужно сделать.
Явный вызов и объявление статических полей
1) Как сделать невозможным явный вызов деструктора и операторов? class MyClass < private : int.
Вызов конструкторов
привет почему здесь вызывается только 1 конструктор? #include <iostream> using namespace.