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

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

Конструктором копирования (в англоязычной литературе используется термин copy constructor) называется специальный конструктор в языке программирования C++, применяемый для создания нового объекта как копии уже существующего. Такой конструктор принимает как минимум один аргумент: ссылку на копируемый объект.

Обычно компилятор автоматически создает конструктор копирования для каждого класса (известные как неявные конструкторы копирования, т.е. конструкторы копирования, заданные неявным образом), но в некоторых случаях программист создает конструктор копирования, называемый в таком случае явным конструктором копирования (или "конструктором копирования, заданным явным образом"). В подобных случаях компилятор не создает неявные конструкторы.

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

Содержание

Определение

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

X(const X&);
X(X&);
X(const volatile X&);
X(volatile X&);
X(const X&, int = 10);
X(const X&, double = 1.0, int = 40);

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

X a = X();     // корректно, если есть X(const X&) и не корректно, если есть X(X&)
               // так как второй вариант не поддерживает переменные

Другое отличие между ними довольно наглядно:

const X a;
X b = a;       // корректно, если есть X(const X&) и не корректно, если есть X(X&)
               // так как второй не поддерживает тип const X&

Вид X& конструктора копирования используется когда необходимо изменить копируемый объект. Это довольно редкая ситуация, но она предусмотрена в стандартной библиотеке вызовом std::auto_ptr. Ссылка должна реализовывать:

X a;
X b = a;       // корректно, если определен любой из конструкторов копирования
               // с момента передачи ссылки

Следующие конструкторы копирования (или постоянные конструкторы) некорректны:

X(X);
X(const X);

так как вызов этих конструкторов потребует очередного копирования, что приведет к бесконечному рекурсивному вызову (т.е. бесконечный цикл).

Существует четыре случая вызова конструктора копирования:

  1. Когда объект возвращает значение
  2. Когда объект передается (функции) по значению в качестве аргумента
  3. Когда объект конструируется на основе другого объекта (того же класса)
  4. Когда компилятор генерирует временный объект (как в первом и втором случаях выше; как явное преобразование и т.д.)

Операции

Объекту может быть присвоено значение при помощи одного из двух способов:

  • Явное присваивание в выражении
  • Инициализация

Явное присваивание в выражении

Object A;
Object B;
A = B;       // транслируется как Object::operator=(const Object&), таким образом вызывается A.operator=(B)

Инициализация

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

a. При помощи объявления

Object B = A; // транслируется как Object::Object(const Object&)

b. При помощи аргументов функции

type function (Object a);

c. При помощи возвращаемого значения функции

Object a = function();

Конструктор копирования используется только в последнем случае (инициализации) и не используется вместо присваивания (т.е. там, где используется оператор присваивания).

Неявный конструктор копирования класса вызывает базовые конструкторы копирования и копии их членов, соответствующие их типу. Если это тип класса, то вызывается конструктор копирования. Если это скалярный тип, то используется встроенный оператор присваивания. И наконец, если это массив, то каждый элемент копируется соответствующим их типу образом.[2]

Применением явного конструктора копирования программист может определить дальнейшие действия после копирования объекта.

Примеры

Следующие примеры иллюстрируют как работают конструкторы копирования и почему они иногда требуются.

Неявный конструктор копирования

Рассмотрим следующий пример.

#include <iostream>
 
class Person
{
    public:
        int age;
        Person(int age) : age(age) {}
};
 
int main()
{
    Person timmy(10);
    Person sally(15);
 
    Person timmy_clone = timmy;
 
    std::cout << timmy.age << " " << sally.age << " " << timmy_clone.age << std::endl;
 
    timmy.age = 23;
 
    std::cout << timmy.age << " " << sally.age << " " << timmy_clone.age << std::endl;
}

Результат

10 15 10
23 15 10

Как и ожидалось, timmy скопировался в новый объект timmy_clone. При изменении возраста (age) timmy, у timmy_clone возраст не менялся. Это потому что они являются полностью независимыми объектами.

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

Person(Person const& copy)
  : age(copy.age) {}

Так когда же нам реально требуется явный конструктор копирования? В следующем подразделе рассмотрим этот вопрос.

Явный конструктор копирования

Теперь рассмотрим очень простой класс динамических массивов, как например, нижеследующий:

#include <iostream>
 
class Array
{
  public:
    int size;
    int* data;
 
    Array(int size)
        : size(size), data(new int[size]) {}
 
    ~Array() 
    {
        delete[] data;
    }
};
 
int main()
{
    Array first(20);
    first.data[0] = 25;
 
    {
        Array copy = first;
 
        std::cout << first.data[0] << " " << copy.data[0] << std::endl;
 
    }    // (1)
 
    first.data[0] = 10;    // (2)
}

Результат

25 25
Segmentation fault

Хотя мы не указывали конструктор копирования, компилятор сгенерировал его для нас. Генерируемый конструктор выглядит примерно так:

Array(Array const& copy)
  : size(copy.size), data(copy.data) {}

Проблема, связанная с этим конструктором, заключается в том, что он выполняет простое копирование указателя data. Он только копирует адрес, а не сами данные. И когда программа доходит до строчки (1), вызывается деструктор copy (объекты в стеке уничтожаются автоматически при достижении их границ). Как видно, деструктор Array удаляет массив data, поэтому когда он удаляет данные copy, он также удаляет данные first. Строка (2) теперь получает неправильные данные и записывает их! Это и приводит к знаменитой ошибке сегментации (segmentation fault).

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

Array(Array const& copy)
  : size(copy.size), data(new int[copy.size]) 
{
    std::copy(copy.data, copy.data + copy.size, data);    // #include <algorithm> для std::copy
}

Здесь мы создаем новый массив int и копируем содержимое в него. Теперь, деструктор copy только удалит его данные и не тронет данные first. Строка (2) больше не вызывает ошибку сегментации.

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


Конструкторы копирования и шаблоны

Вопреки ожиданиям шаблон конструктора копирования - не явный конструктор копирования. Таким образом этого будет недостаточно:

template <typename A> Array::Array(const Array& copy)
  : size(copy.size()), data(new int[copy.size()]) 
{
   std::copy(copy.begin(),copy.end(),data);
}

Явный, т.е. не шаблонный, конструктор копирования должен также реализовывать массив (Array) на основе массива (Array).

Смотри также

Примечания

  1. INCITS ISO IEC 14882-2003 12.8.2. [1]
  2. INCITS ISO IEC 14882-2003 12.8.8. [2]

Wikimedia Foundation. 2010.

Игры ⚽ Поможем решить контрольную работу

Полезное


Смотреть что такое "Конструктор копирования" в других словарях:

  • Конструктор (программирование) — У этого термина существуют и другие значения, см. Конструктор. В объектно ориентированном программировании конструктор класса (от англ. constructor, иногда сокращают ctor)  специальный блок инструкций, вызываемый при создании объекта.… …   Википедия

  • Конструктор класса — В объектно ориентированном программировании конструктор класса (от англ. constructor, иногда сокращают ctor)  специальный блок инструкций, вызываемый при создании объекта, причём или при его объявлении (располагаясь в стеке или в статической… …   Википедия

  • Конструктор объекта — В объектно ориентированном программировании конструктор класса (от англ. constructor, иногда сокращают ctor)  специальный блок инструкций, вызываемый при создании объекта, причём или при его объявлении (располагаясь в стеке или в статической… …   Википедия

  • ГЛАВНЫЙ КОНСТРУКТОР ПРОЕКТА — Должностные обязанности. Осуществляет руководство разработкой комплексных проектов на всех стадиях и этапах выполнения работ, обеспечивая при этом их экономическую эффективность и конкурентоспособность, высокий , эксплуатационные удобства,… …   Квалификационный справочник должностей руководителей, специалистов и других служащих

  • ВЕДУЩИЙ КОНСТРУКТОР — Должностные обязанности. Осуществляет руководство и организацию отдельных стадий или направлений проектно конструкторских работ, участвует в решении всех связанных с этим научно технических вопросов. Разрабатывает технические задания на… …   Квалификационный справочник должностей руководителей, специалистов и других служащих

  • ГЛАВНЫЙ КОНСТРУКТОР — Должностные обязанности. Руководит созданием новых и модернизацией конструкций изделий (комплексов, машин, аппаратов, приборов, механизмов) действующего производства, обеспечивая их высокий , конкуренто и патентоспособность, соответствие… …   Квалификационный справочник должностей руководителей, специалистов и других служащих

  • C++11 — C++11[1][2] или ISO/IEC 14882:2011[3] (в процессе работы над стандартом носил условное наименование C++0x[4][5])  новая версия стандарта языка C++, вместо ранее действовавшего ISO/IEC 14882:2003. Новый стандарт включает дополнения в ядре… …   Википедия

  • Оператор присваивания в C++ — Операция присваивания в языке программирования C++ обозначается знаком = . Как и другие операции в C++, она может быть перегружена. Операция присваивания копированием особый вид операции присваивания, используемый для присваивания объектов одного …   Википедия

  • Операция присваивания в С++ — Операция присваивания в языке программирования C++ обозначается знаком = . Как и другие операторы в C++, она может быть перегружена. Операция присваивания копированием особый вид операции присваивания, используемый для присваивания объектов… …   Википедия

  • Одиночка (шаблон проектирования) — У этого термина существуют и другие значения, см. Одиночка. Шаблон проектирования Одиночка Singleton Тип: порождающий Описан в Design Patterns Да Одиночка (англ. Singleton) в программировании  …   Википедия


Поделиться ссылкой на выделенное

Прямая ссылка:
Нажмите правой клавишей мыши и выберите «Копировать ссылку»