Чем отличается конструктор копирования от оператора присваивания

Обновлено: 28.03.2024

Я прошел [вопрос] (В чем разница между оператором присваивания и конструктором копирования? ) и понял разницу между конструктором копирования и оператором присваивания. Теперь мой вопрос хотя конструктор копирования инициализирует ранее неинициализированный объект, где оператор присваивания заменяет данные из ранее инициализированного объекта какая разница с точки зрения конечного результата. Я думаю, что окончательный результат в обоих случаях оказывается одинаковым, верно? В итоге после копирования через (CC) мы получаем тот же вывод, и даже после использования (AO) мы получаем тот же вывод. Имею ли я здесь смысл? Может кто-нибудь уточнить, в чем разница с точки зрения реального применения?

Решение

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

Другая причина — некоторые нетривиальные классы не имеют (общедоступного) конструктора по умолчанию и могут быть созданы только путем копирования существующих объектов откуда-то.

Другие решения

Конечный результат полностью зависит от ситуации. Вам просто нужно понять это

Конструктор копирования вызывается, когда новый объект создается из существующего объекта, как копия существующего объекта.

Оператор присваивания вызывается, когда оба объекта уже созданы, и мы присваиваем один другому. например: а = б

Разница:

Конструктор копирования создает новый объект, который имеет копию исходного объекта.

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

Для копирования указателя нам нужно Глубокая копия как конструктор копирования компилятора по умолчанию и оператор присваивания Мелкая копия . Так что проверьте это для более глубокого понимания Мелкая копия против глубокой копии

Это зависит от тебя. Язык не предъявляет никаких требований к
Относительно того, что на самом деле делает конструктор копирования или присвоение.
Конечно, любые клиенты будут ожидать, что наблюдаемое состояние
объект должен быть таким же, как у скопированного объекта, в обоих
случаи; это включает в себя код в стандартной библиотеке, которая может копировать
или присвойте вашим типам объектов. Но есть законные случаи
где класс может содержать метаинформацию, используемую для
инструменты или управление ресурсами, которые могут быть разными
в двух случаях. (Это также часто для класса, чтобы поддержать
копировать конструкцию, для использования в clone функционировать, но не
назначение поддержки.)

Конечно, реализация может быть очень разной; если
объект содержит указатель на динамически выделяемую память, для
пример:

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

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

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

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

1. Конструктор копирования

Конструктор копирования, в отличии от других, в качестве параметра принимает константную ссылку на объект класса.

Данный конструктор вызывается всякий раз, когда создаётся новый объект и для его инициализации берётся значение существующего объекта того же типа. Например, в следующих случаях:

Также конструктор копирования вызывается при передаче объекта в функцию или возврате из неё по значению. Аналогично, с помощью конструктора копирования создаются временные объекты при вычислении арифметических и других операций.

В чём же проблема отсутствия конструктора копирования при выделении в классе динамической памяти? Дело в том, что при отсутствии явного описания, он описывается неявно. Неявный конструктор выполняет поверхностное копирование, т. е. просто дублирует биты из переменных. Таким образом, вместо данных из динамической памяти, копируется адреса на них. В результате, появляется несколько объектов, указывающих на одну область памяти. При изменении этой области через один объект, она также изменится и в другом, что в большинстве случаев является нежелательным поведением. Поэтому в классах, работающих с динамической памятью, необходимо всегда явно объявлять конструктор копирования (см. пример в конце). Как вариант исключения данной проблемы, можно поместить конструктор копирования в приватной области класса, что вовсе запретит выполнять копирование.

2. Перегруженная операция присваивания

Перегруженная операция присваивания используется при присваивании одного объекта другому существующему объекту. Здесь присутствует такая же проблема, что и в конструкторе копирования. К тому же, у объекта, которому присваивается значение, уже может быть выделена динамическая память. Перед присваиванием новых данных, выделенную ранее память необходимо очистить, чтобы не допустить её утечки (см. пример в конце). Также необходимо обработать случай самоприсваивания. В противном случае, данные в динамической памяти просто будут утеряны. Аналогично копированию, присваивание также можно запретить, поместив операцию в приватной области класса.

3. Деструктор

Деструктор вызывается перед удалением объекта и предназначен для освобождения всех используемых ресурсов. Чтобы не допустить утечки памяти, в деструкторе необходимо её очистить.

4. Пример

Стоить отметить, что во всех трёх функциях память должна выделяться и удаляться одинаковым образом. Т. е. нельзя в одном случае использовать delete, а в другом delete[].

Есть ощущение что я не совсем понимаю (или совсем не понимаю) как работает перемещение (по rvalue ссылкам) в С++, и как правильно следует организовывать/использовать конструкторы/операторы перемещения-копирования в классах.

Немного о сложившейся ситуации

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

Затем я попытался сделать примерно такую штуку:

И в итоге получилось так, что указатель data в объекте a более не валидный (а при завершении программы/функции вообще все ломается). По моим предположениям это происходит из-за того что объект, который я получаю путем вызова конструктора A(100) , и который затем присваиваю в a - он как бы временный, и после присвоения тут же срабатывает его деструктор, очищая то, что лежит по указателю data . Получается что значение указателя копируется, а данные уже убиты деструктором (или я не прав?).

Первой мыслью, которая пришла в голову, была "не хватает конструктора копирования". Я решил его написать, выглядел он как-то вот так:

Но это не помогло. Похоже в операции a = A(100); конструктор копирования вовсе не участвует. Немного погуглив - наткнулся на различные идиомы "перемещения/присваивания/копирования", толком не поняв сути, решил что не хватает конструктора перемещения. Добавил его, как-то так он выглядел:

И тут произошло нечто мистическое. Среда начала подчеркивать оператор = как ошибку, там где я пытался сделать a = A(100) , а код вообще перестал компилироваться. Среда давала такие пояснения - "на функцию A::oprator=(A const a&) нельзя ссылаться, так как эта функция удалена". Куда удалена? Почему удалена? Что вообще происходит.. не знаю. В итоге я решил таки переопределить этот самый оператор = , он получился примерно таким:

И о чудо, теперь все работает.

Только вот осталась куча вопросов:

Что я вообще такое сделал? Когда именно вызывается конструктор перемещения а когда конструктор копирования? Почему при переопределении конструктора перемещения A(A&& other):A() перестает работать оператор = и его надо явно определять? В чем смысл в операторе = делать почти то же самое что и в конструкторе перемещения, я ведь мог бы обойтись только оператором перемещения? Как вообще правильно ко всему этому подойти?

Что-то я совсем запутался, был бы рад последовательному объяснению, по пунктам.

А использовали бы обычный std::vector (как рекомендует Страуструп) и горя бы не знали, и move семантику даже бы и не изучили.

3 ответа 3

Нашли верное решение - написать всю большую тройку (ныне пятерку :)) копирующий конструктор, деструктор и оператор присвоения. Теперь еще (при необходимости) добавляются перемещающий конструктор и перемещающее присваивание.

Когда именно вызывается конструктор перемещения а когда конструктор копирования?

Когда создается экземпляр. Например,

В новом стандарте есть свои тонкости - так, в

копирующий/перемещающий конструктор не вызывается.

Почему при переопределении конструктора перемещения A(A&& other):A() перестает работать оператор = и его надо явно определять?

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

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

Вряд ли в ситуации a = b; можно обойтись оператором перемещающего присваивания - тогда b у вас был бы пустым после присваивания.

Но есть распространенная идиома присваивания через копирование и обмен - если есть функция swap , обменивающая внутренние представления двух объектов (вот тут - просто меняя указатели), то присваивание можно сделать как

Как вообще правильно ко всему этому подойти?

Почитать соответствующую литературу. Например, Мейерса "Эффективный и современный C++". Большой список есть здесь.

Есть ощущение что я не совсем понимаю (или совсем не понимаю) как работает перемещение (по rvalue ссылкам) в С++

Тут сразу можно заметить, что "перемещение" в С++ работает так, как вы его сами реализуете. В самом языке (в ядре языка) никакого "перемещения" нет. Есть только тип rvalue-ссылки со своими правилами поведения в процессе разрешения перегрузок (overload resolution). А уж воспользоваться этим типом rvalue-ссылки и сопутствующими ему правилами разрешения перегрузок для целей "перемещения" - ваша задача.

Однако вас никто не заставляет это делать. Перемещение - это во многих случаях лишь оптимизационная возможность. При этом с концептуальной точки зрения обычное "копирование" - это частный случай "перемещения". "Копирование" есть наименее оптимальный вариант "перемещения". То есть вас никто никогда не заставляет реализовывать перемещение там, где у вас уже реализовано копирование. Копирование и так само со всем справится, пусть и менее оптимально.

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

Затем я попытался сделать примерно такую штуку:

[. ] он как бы временный, и после присвоения тут же срабатывает его деструктор, очищая то, что лежит по указателю data. Получается что значение указателя копируется, а данные уже убиты деструктором (или я не прав?).

Все совершенно верно.

Первой мыслью, которая пришла в голову, была "не хватает конструктора копирования".

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

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

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

решил что не хватает конструктора перемещения.

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

Это так называемое классическое Правило Трех.

Добавил его [. ] Среда давала такие пояснения - "на функцию A::oprator=(A const a&) нельзя ссылаться, так как эта функция удалена". Куда удалена? Почему удалена? Что вообще происходит..

В современном С++ бытует вполне резонное мнение, что языку изначально следовало бы более строго требовать от пользователя соблюдения Правила Трех. А именно: в ситуациях, когда пользователь объявляет в своем классе хотя бы одну из функций Правила Трех (конструктор копирования, копирующий оператор присваивания, деструктор), автоматически подавлять неявную генерацию всех остальных функций Правила Трех. То есть язык должен заставлять пользователя действовать по принципу "реализовал руками одну - тогда реализуй руками и все остальные". Однако в классическом С++ этого сделано не было. Вы как раз стали жертвой этой ситуации: вы реализовали конструктор копирования и деструктор, но "забыли" реализовать соответствующий им оператор присваивания.

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

В итоге я решил таки переопределить этот самый оператор =, он получился примерно таким:

Прекрасно. При этом для достижения формальной работоспособности кода вам на самом деле "не нужен" ваш конструктор перемещения. Но для оптимизации он вполне полезен.

То, как вы реализовали ваш оператор присваивания (получение параметра "по значению" и swap ) как раз является наиболее простым способом оптимизировать код на основе семантики перемещения.


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

Давайте изучим разницу между конструктором копирования и оператором присваивания.

Сравнительная таблица

Основа для сравненияКопировать конструкторОператор присваивания
основнойКонструктор копирования является перегруженным конструктором.Оператор присваивания является побитовым оператором.
Имея в видуКонструктор копирования инициализирует новый объект уже существующим объектом.Оператор присваивания присваивает значение одного объекта другому объекту, оба из которых уже существуют.
Синтаксисимя_класса (продолжение имя_класса и имя_объекта) <
// тело конструктора
>
имя_класса Ob1, Ob2;
Ob2 = OB1;
Запускает(1) Конструктор копирования вызывается, когда новый объект инициализируется с существующим.
(2) Объект передается функции в качестве нереферентного параметра.
(3) Объект возвращается из функции.
Оператор присваивания вызывается только при назначении существующему объекту нового объекта.
Выделение памятиИ целевой объект, и инициализирующий объект находятся в разных местах памяти.И целевой объект, и инициализирующий объект используют одну и ту же выделенную память
По умолчаниюЕсли вы не определяете конструктор копирования в программе, компилятор C ++ неявно предоставляет его.Если вы не перегружаете оператор «=», будет сделана побитовая копия.

Определение Копирующего Конструктора

«Конструктор копирования» - это форма перегруженного конструктора . Конструктор копирования вызывается или вызывается только для целей инициализации. Конструктор копирования инициализирует вновь созданный объект другим существующим объектом. Когда конструктор копирования используется для инициализации вновь созданного целевого объекта, тогда и целевой объект, и исходный объект совместно используют разные области памяти. Изменения, внесенные в исходный объект, не отражаются в целевом объекте. Общая форма конструктора копирования

Если программист не создает конструктор копирования в программе на C ++, то компилятор неявно предоставляет конструктор копирования. Неявный конструктор копирования, предоставляемый компилятором, делает членскую копию исходного объекта. Но иногда для каждого члена копии недостаточно, поскольку объект может содержать переменную-указатель. Копирование переменной указателя означает, что мы копируем адрес, сохраненный в переменной указателя, но мы не хотим копировать адрес, сохраненный в переменной указателя, вместо этого мы хотим скопировать то, на что указывает указатель. Следовательно, существует необходимость в явном «конструкторе копирования» в программе для решения такого рода проблем.

Конструктор копирования вызывается в трех условиях:

  • Конструктор копирования вызывается, когда новый объект инициализируется с существующим.
  • Объект передан функции в качестве нереферентного параметра.
  • Объект возвращается из функции.

Давайте разберемся с конструктором копирования на примере.

В приведенном выше коде я явно объявил конструктор «copy (copy & c)». Этот конструктор копирования вызывается, когда объект B инициализируется с использованием объекта A. Во второй раз он вызывается, когда объект C инициализируется с использованием объекта A. Когда объект D инициализируется с использованием объекта A, конструктор копирования не вызывается, поскольку при инициализации D оно уже существует, а не вновь созданное. Следовательно, здесь вызывается оператор присваивания.

Определение оператора присваивания

Оператор присваивания является оператором присваивания C ++. Оператор «=» используется для вызова оператора присваивания. Он копирует данные в одном объекте идентично другому объекту. Оператор присваивания копирует один объект в другой для каждого члена. Если вы не перегружаете оператор присваивания, он выполняет побитовое копирование. Поэтому вам необходимо перегрузить оператор присваивания.

В приведенном выше коде, когда объект A назначен объекту B, оператор присваивания вызывается, поскольку оба объекта уже существуют. Аналогичным образом, тот же самый случай, когда объект C инициализируется с объектом A.

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

Ключевые различия между конструктором копирования и оператором присваивания

  1. Конструктор копирования является перегруженным конструктором, где в качестве оператора присваивания используется побитовый оператор.
  2. Используя конструктор копирования, вы можете инициализировать новый объект уже существующим объектом. С другой стороны, оператор присваивания копирует один объект в другой объект, оба из которых уже существуют.
  3. Конструктор копирования инициализируется всякий раз, когда новый объект инициализируется уже существующим объектом, когда объект передается в функцию в качестве параметра без ссылки или когда объект возвращается из функции. С другой стороны, оператор присваивания вызывается только тогда, когда объект назначается другому объекту.
  4. Когда объект инициализируется с помощью конструктора копирования, инициализирующий объект и инициализированный объект совместно используют разные места в памяти. С другой стороны, когда объект инициализируется с использованием оператора присваивания, тогда инициализированный и инициализирующий объекты совместно используют одну и ту же область памяти.
  5. Если вы явно не определяете конструктор копирования, тогда его предоставляет компилятор. С другой стороны, если вы не перегружаете оператор присваивания, то выполняется операция побитового копирования.

Заключение:

Конструктор Copy лучше всего подходит для копирования одного объекта в другой, когда объект содержит необработанные указатели.

Я не понимаю разницы между конструктором присваивания и конструктором копирования в С++. Это так:

Я хочу знать, как выделить память конструктора присваивания и конструктора копирования?

задан 29 июля '12, 02:07

8 ответы

A конструктор копирования используется для инициализации ранее неинициализированный объект из данных какого-либо другого объекта.

An оператор присваивания используется для замены данных ранее инициализированный объект с некоторыми данными другого объекта.

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

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

ответ дан 24 апр.

Просто примечание: в настоящее время (начиная с С++ 11) они могут быть явно установлены по умолчанию с помощью =default; . - Дедупликатор

@Deduplicator Также важно отметить, что, придерживаясь классификаций, требующих тривиальных конструкторов, вы должен = default там, где требуется ctor по умолчанию: простая реализация пустого тела самостоятельно по-прежнему считается определяемым пользователем ctor и, следовательно, (на стандартном уровне) не является тривиальной и исключает тип из классификаций, требующих тривиального ctor. - underscore_d

@sbi Могу ли я сказать, что в случае, если конструктор копирования не используется, а вместо него используется оператор присваивания, объект сначала создается путем вызова конструктора либо с аргументами, либо без аргументов, а затем используется оператор присваивания, и новые значения назначаются на основе RHS. В случае использования конструктора копирования будет вызываться тот же конструктор, но значения, используемые для инициализации, взяты из другого объекта. - Раджеш

@Rajesh: Я не понимаю, о чем ты спрашиваешь, и я чувствую, что это потому, что ты тоже в замешательстве. :) Вы попытаетесь еще раз объяснить, о чем вы говорите? - SBI

@CătălinaSîrbu: Вы могли бы. Это две независимые функции. - SBI

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

  • Если перед копированием необходимо создать новый объект, используется конструктор копирования.
  • Если перед копированием не нужно создавать новый объект, используется оператор присваивания.

Пример оператора присваивания:

Пример конструктора копирования:

Будет ли справедливо сказать, что оператор присваивания эффективно сочетает уничтожение старого объекта с созданием нового, но с оговоркой, что (1) если один из шагов уничтожения старого объекта будет отменен один из шагов построения нового, оба шага могут быть опущены; (2) операторы присваивания не должны делать плохих вещей, если объект присваивается самому себе. - Supercat

Первый — инициализация копирования, второй — просто присваивание. Нет такой вещи, как конструктор присваивания.

использует сгенерированный компилятором конструктор копирования.

использует конструктор по умолчанию для построения cc , а затем *оператор присваивания** ( operator = ) на уже существующем объекте.

Я хочу знать, как выделить память конструктора присваивания и конструктора копирования?

IDK, что вы подразумеваете под выделением памяти в этом случае, но если вы хотите увидеть, что происходит, вы можете:

Так же рекомендую посмотреть:

ответ дан 23 мая '17, 13:05

Конструктор копирования вызывается, когда новый объект создается из существующего объекта как копия существующего объекта. А оператор присваивания вызывается, когда уже инициализированному объекту присваивается новое значение из другого существующего объекта.

То, что сказал @Luchian Grigore Said, реализовано так

конструктор по умолчанию

конструктор по умолчанию

ответ дан 03 мар '14, в 07:03

разница между конструктором копирования и конструктором присваивания заключается в следующем:

  1. В случае конструктора копирования он создает новый объект. ( = )
  2. В случае конструктора присваивания он не будет создавать никаких объектов, что означает, что он применяется к уже созданным объектам ( = ).

И основные функции в обоих одинаковы, они будут копировать данные из o2 в o1 член за членом.


Хочу добавить еще один момент по этой теме. «Операторная функция оператора присваивания должна быть написана только как функция-член класса». Мы не можем сделать его дружественной функцией, в отличие от других бинарных или унарных операторов.

ответ дан 18 авг.

Что-то добавить о конструкторе копирования:

При передаче объекта по значению будет использоваться конструктор копирования

Когда объект возвращается из функции по значению, он будет использовать конструктор копирования

При инициализации объекта с использованием значений другого объекта (как в приведенном вами примере).

Не тот ответ, который вы ищете? Просмотрите другие вопросы с метками c++ memory or задайте свой вопрос.

Последние вопросы

Связанные теги

Данный сайт использует файлы cookie, чтобы предоставить Вам наилучший сервис. Продолжая использовать сайт, Вы принимаете нашу политику в отношении файлов cookie. OkПодробнее

Читайте также: