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

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

Оператор присваивания и конструктор копирования

Хочу разобраться с вызовом конструктора копирования и оператора присваивания в классе. Правда ли, что здесь вызовется конструктор копирования и только он:

SomeClass A = B; 

А здесь только оператор присваивания:

Matrix A, B; A = B; 

Если так, то чем отличается конструктор копирования от оператора присваивания, почему нельзя заменить один другим и зачем в С++ они разделены?

Отслеживать
9,366 4 4 золотых знака 40 40 серебряных знаков 56 56 бронзовых знаков
задан 2 июн 2017 в 16:18
671 4 4 серебряных знака 17 17 бронзовых знаков

Вы забываете, что в вашем втором примере вызывается сначала конструктор, а потом только оператор присваивания.

2 июн 2017 в 16:46

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Задача любого конструктора — создать в сырой неинициализированной памяти объект, которого до этого момента еще не существовало. В общем случае, объект, который «видит» конструктор в начале своей работы, содержит непредсказуемый мусор. Задача конструктора — проинициализировать объект, т.е. придать всем его компонентам осмысленные значения.

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

Это две совершенно разные задачи.

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

То есть упрощенно можно сказать, что конструктор копирования — это «половинка» оператора присваивания.

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

Классы: копирование и присваивание

В этой части мы продолжим начатое в статье Элементы класса, о которых всегда необходимо помнить обсуждение конструктора копий (copy constructor) и операции присваивания (assignment operator). Или, вернее, начнем подробное рассмотрение весьма нетривиальной проблемы, каковой на самом деле является копирование и присваивание в классах.
Эти два элемента вполне заслужили отдельного рассмотрения. Создание программ на C++ без понимания внутренней сущности этих функций-членов сродни бегу на марафонскую дистанцию без тренировки (возможно, это не самое удачное сравнение, проще говоря, эти функции очень важны).
Конструктор копий служит для создания новых объектов из существующих. Операция присваивания нужна для того, чтобы сделать один существующий объект эквивалентным другому существующему.
Что означает создать копию? Как один из вариантов, это означает присваивание значений элементов одного объекта элементам другого. Этот ответ, однако, далеко не полон. C++ — это язык, который практически не ограничивает выбор пути реализации программы. И способ создания копий объектов — не исключение из этого правила.
Иногда для копирования классов достаточно просто привести один объект в то же состояние, что и другой. Это весьма просто, и мы увидим, как это делается. Однако если вашему приложению требуются другие методы копирования, C++ не станет создавать их за вас, хотя, если вы не напишете эти функции, компилятор сделает это сам. Правда, результат при этом может существенно отличаться от того, что вам бы хотелось.

  • Понятие копирования
  • Копирование буквальное и развернутое
  • Когда выполняется копирование
  • Разница между копированием и присваиванием
  • Положение в классах
  • Блокирование копирования и присваивания
  • Реализация копирования через присваивание
  • Копирование и присваивание в производных классах

Здесь мы поговорим об одном из аспектов внутреннего функционирования программ, написанных на C++ — о копировании. Копирование в программах на C++ происходит, прямо или косвенно, буквально на каждом шагу. Причем, не всегда с первого взгляда очевидно, где происходит копирование, а где нет.
Мы начнем с рассмотрения синтаксиса интересующего нас предмета, а затем попробуем углубиться в его осмысление.

Определение конструктора копий

Конструктор копий используется для создания новых объектов из уже существующих. Это означает, что, так же как для других конструкторов, новый объект еще не существует к моменту его вызова. Однако только конструктору копий объект передается как аргумент по ссылке. Итак, синтаксис конструктора копий прост. Конструктор копий произвольного класса X выглядит так:
Х(const X&) ; // конструктор копий класса Х
Так как конструктор копий — это все таки конструктор, то он должен иметь имя, совпадающее с именем класса (не забывайте с учетом регистра символов). Назначение конструктора копий — дублирование объекта-аргумента для построения нового объекта. Одно из основных правил: если аргумент не должен изменяться, то его следует передавать как константу. В то же время, если аргумент не описан как константа, то нельзя копировать объекты-константы. Переменный объект всегда можно передать как постоянный аргумент, но не наоборот.
Вторая часть объявления аргумента, X, проста: копируется объект того же самого типа. Аргумент в целом читается как «постоянная ссылка на X». Ссылка существенна по нескольким соображениям. В первую очередь потому, что при передаче адреса объекта не создается копия вызывающего объекта (в отличие от передачи аргумента по значению). Если вам чудится здесь какой-то подвох, то будьте внимательны.
Работа конструктора копий — создание ранее не существовавшего объекта из уже существующего, а передача по значению (без использования операции получения адреса) требует создания копии аргумента, значит мы получаем бесконечную рекурсию. Точнее: при передаче объекта по значению создается его копия, если это произойдет в конструкторе копий, то он будет вызывать сам себя, пока не исчерпает все ресурсы системы.

  • Имя функции точно совпадает с именем класса.
  • Аргумент объявляется постоянным, что позволяет принимать как постоянные, так и переменные аргументы.
  • Тип аргумента является типом класса.
  • Аргумент передается по ссылке, т. е. с помощью операции получения адреса.
  • Среди членов класса нет указателей (*).
  • Среди членов класса нет ссылок (&).

class X
public:
Х(); // конструктор по умолчанию
virtual ~X(); // виртуальный деструктор
// Конструктор копии и операция присваивания не определены
// намеренно. Класс содержит только данные, размещаемые
// в стеке, поэтому предопределенных конструктора копий
//и операции присваивания достаточно.
private:
int data;
char moreData;
float no_Pointers;
>;

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

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

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

X& operator=(const X&); // синтаксис операции присваивания для
// произвольного класса

Присваивание — это операция, значит мы должны использовать ключевое слово operator и соответствующий символ операции. Так как C++ допускает цепочки присваивания
а = b = с = d; // C++ допускает последовательные присваивания,
// так что это свойство надо сохранить
то необходимо возвращать ссылку на объект; в противном случае цепочка прервется.
Итак, оператор-функция принимает постоянную ссылку, а возвращает ссылку на объект. Использование ключевого слова const позволяет функции работать как с постоянными объектами, так и с переменными.

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

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

POINT Pix;
Pix = Pix; // присваивание самому себе

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

POINT& POINT::operator=(const POINT& rhs)
if(this == &rhs) return *this; // проверка на присваивание себе
else < X=rhs.X; Y=rhs.Y; >//то, что делает оператор полезного
return *this; // возврат ссылки на объект
>

Сейчас мы попробуем в ней разобраться, благо для всех операций присваивания проверка на присваивание себе совершенно одинакова.
Оператор if(this == &rhs) проверяет, не совпадает ли аргумент с самим объектом. Указатель this содержит адрес вызывающего объекта, &rhs читается как «адрес rhs». Таким образом, сравниваются два адреса. Если они эквивалентны (==), то это один и тот же объект. В этом случае, в полном соответствии с требованием возврата ссылки на объект, просто возвращаем *this (заметьте, что в конце функции делается то же самое) и выходим из функции.
Вспомните, что this — это указатель. Значение указателя — это адрес. Чтобы получить значение указателя, его следует разыменовывать. Разыменование указателя выглядит так: *ptr. Указатель this разыменовывается точно так же: *this.
Помещая эти две строки в начале и в конце тела операции присваивания, мы уменьшаем вероятность возникновения утечек памяти из-за этой операции.
Если вы запомнили приведенный здесь синтаксис копирования и присваивания и, определяя новый класс, сразу будете определять и их тоже, то это уже полдела.

Зачем C++ требует определения этих функций-членов?

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

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

POINT х;
POINT у(х); // Прямой вызов конструктора копий.
POINT х = у; // Выглядит как присваивание, но на самом деле
// вызывает конструктор копий. Почему? См. ниже.
POINT a, b;
a = b; // Вызов операции присваивания
POINT Foo(); // Возврат по значению, вызывает копирование
void Foo(POINT); // Передача по значению, создает копию

Во всех этих случаях выполняется копирование. В ходе выполняемой компилятором оптимизации могут появиться и другие варианты. Это та область, где знание действительно сила, способная помочь вам избежать утечек памяти.
В операторе типа POINT х = у; не вызывается операция присваивания класса POINT, хотя на первый взгляд выглядит это именно так. Причина состоит в том, что операция присваивания — это функция-член, а значит может быть вызвана только для уже существующих объектов, в то время как в этом фрагменте происходит создание нового объекта х.
Если объект создается в той же строке, в которой он выступает в качестве левостороннего аргумента, то вызывается конструктор. Строка

Х х = у; // вызов конструктора копий
эквивалентна строке
Х х(у); // вызов конструктора копий
|что совсем не то же самое, что
Х х, у;
х = у; // вызов операции присваивания

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

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

При написании статьи использованы материалы из книг
P.Kimmel
Using Borland C++ 5
Special Edition
перевод BHV — С.Петербург 1997

C++. Бархатный путь
Марченко А.Л.
Центр Информационных Технологий
www.citmgu.ru

Thinking in C++, 2nd ed. Volume 1
2000 by Bruce Eckel

Если вам интересно, или возникают вопросы пишите, разберемся.
Сергей Малышев (aka Михалыч).

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

Скачай курс
в приложении

Перейти в приложение
Открыть мобильную версию сайта

© 2013 — 2023. Stepik

Наши условия использования и конфиденциальности

Get it on Google Play

Public user contributions licensed under cc-wiki license with attribution required

В чем разница между перегруженным оператором «=» и конструктором копирования?

Разве оператор присвоения нельзя перегрузить так, чтобы он работал на не инициализированные объекты? Даже если есть различия в использовании, могут ли они взаимозаменять друг друга?

Лучшие ответы ( 1 )
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:

В чем разница между конструктором с параметрами и конструктором преобразования?
В википедии указано, что существуют конструкторы: — Конструктор по умолчанию — Конструктор с.

В чем разница между конструктором по умолчанию и конструктором с параметрами?
Объясните пожалуйста в чем разница между конструктором по умолчанию и конструктором с параметрами.

В чем разница между оператором += и методом AppendText()
В чем разница для textbox между оператором += и методом AppendText() textbox1.Text += "Some.

В чем разница между логическим и условным оператором «или»?
В чем разница между | и ||?

«C with Classes»
1641 / 1398 / 523
Регистрация: 16.08.2014
Сообщений: 5,855
Записей в блоге: 1

YelloveCF, оператор присваивания ты никак не вызовешь на не инициализированном объекте.

Добавлено через 1 минуту
то есть объект должен уже быть, и в нем уже что то есть. а конструктор копирования можно вызывать на пустом объекте которого в принципе еще не существует (образно говоря)

Любитель чаепитий
3741 / 1798 / 565
Регистрация: 24.08.2014
Сообщений: 6,016
Записей в блоге: 1

ЦитатаСообщение от YelloveCF Посмотреть сообщение

Так в чем же разница?

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

ЦитатаСообщение от YelloveCF Посмотреть сообщение

obj a; obj b = a;
в этом случае всё равно вызывается конструктор копирования.
https://rextester.com/TOXGI84672
Mental handicap
1246 / 624 / 171
Регистрация: 24.11.2015
Сообщений: 2,429

Лучший ответ

Сообщение было отмечено YelloveCF как решение

Решение

ЦитатаСообщение от YelloveCF Посмотреть сообщение

В чем разница между перегруженным оператором «=» и конструктором копирования?

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

obj(obj const& other) : data(other.data) .

, отсюда имеем вызов при

obj a; obj b = a; // вызов конструктора копирования

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

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

obj& operator=(obj const& other) {data = other.data; return *this;}

, теперь что бы его вызвать надо сначала инициализировать объект

1 2 3
obj a; obj b; b = a; // вызов оператора присваивания

Вдобавок стоит сказать что с С++11 есть возможность объявить конструктор копирования как explicit

explicit obj(obj const& other) : data(other.data) .

, что как бы будет заставлять писать так:

1 2 3
obj a; obj b = a; // ошибка obj b(a); // ОК, явный вызов конструктора копирования

, т.е. вызывать конструктор явно.
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Помогаю со студенческими работами здесь

Дополнить класс, конструктором по умолчанию, конструктором инициализации, конструктором копирования
Дополнить класс, конструктором по умолчанию, конструктором инициализации, конструктором.

Разница между Методом, Свойством и Конструктором
Господа, наш путь тернист. На курсах заметил, что явно отстаю. Попросил задания у преподавателя.

Дополнить класс перегруженным конструктором, деструктором
Дополнить класс перегруженным конструктором и деструктором #include <iostream> using namespace.

Дополнить класс Fraction перегруженным конструктором
Дополнить класс Fraction перегруженным конструктором, осуществляющим преобразование вещественного.

Проблема с перегруженным оператором +
Здравствуйте, подскажите пожалуйста пожалуйста почему программа падает в конце, через отладчик.

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

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

Или воспользуйтесь поиском по форуму:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *