Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
63
Добавлен:
16.02.2016
Размер:
455 Кб
Скачать

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

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

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

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

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

Замечание. Следует отличать конструктор копирования от оператора присваивания. Например,

AnyClass ob1;

AnyClass func(); //объявление функции

//оb2 инициализируется оb1 с помощью конструктора копирования

AnyClass ob2(ob1);

//Следующий оператор эквивалентен предыдущему

AnyClass оb2 = оb1;

//Возврат объекта из функции с помощью оператора копирования

оb1 = func();

//Напротив, следующая инструкция использует оператор присваивания оb2 = оb1;

Таким образом, несмотря на наличие в инструкции знака при­сваивания, она не всегда означает выполнение присваивания. Ин­струкция

AnyClass оb2 = оb1 ;

представляет собой просто другую форму записи конструкто­ра копирования.

Устаревшее ключевое слово overload

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

overload <имя_функции>;

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

Перегрузка и неоднозначность. Ключевое слово explicit

Перегруженные функции выбираются по принципу наи­лучшего соответствия объявлений функций в текущей области видимости аргументам, предоставленным в вызове функции. Если подходящая функция найдена, эта функция и вызывается. Слово "подходящая" в этом контексте означает одно из сле­дующего (в порядке ухудшения соответствия):

• найдено точное соответствие;

• выполнено тривиальное преобразование;

• выполнено преобразование целочисленных типов;

• существует стандартное преобразование к желаемому ти­пу аргумента;

• существует определенное программистом преобразование (оператор преобразования или конструктор) к тре­буемому типу аргумента;

• были найдены аргументы, представленные многоточием.

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

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

Функция с n аргументами по умолчанию, с точки зрения со­ответствия аргументов, рассматривается как совокупность из п+1 функций, каждая из которых отличается от предыдущей за­данием одного дополнительного аргумента. Многоточие (...) дей­ствует как произвольный символ: оно соответствует любому за­данному аргументу. Это может служить источником неоднознач­ности при выборе перегруженной функции.

Все сказанное выше относилось ко всем перегруженным функциям, безотносительно к тому, являются ли они функциями-членами или нет. Рассмотрим теперь специфику перегруженных Функций-членов.

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

В отношении перегруженных конструкторов язык C++ пре­доставляет дополнительную возможность по управлению процес­сом поиска соответствия вызываемого конструктора. Ключевое слово explicit представляет собой спецификатор объявления, который может применяться только в объявлениях конструкторов (но не в определениях конструкторов вне класса). Например:

class Т

{

public:

explicit Т (int);

explicit T(double)

{

//тело конструктора

}

};

Т::Т (int)

{

// …

}

Конструктор, объявленный со спецификатором explicit, не принимает участия в неявных преобразованиях типа. Он может быть использован только в том случае, когда никакого преобра­зования типа аргументов не требуется. Например, для класса, объявленного выше:

void f(T) {}; //Определение функции

void g(int i)

{

//Следующая инструкция вызовет ошибку, т.к. неявное преобразование типа int в тип Т запрещено.

f(i);

}

void h()

{

Т ob(10); // А это - верно.

}

Замечание. Нет смысла применять ключевое слово explicit к конструкторам с несколькими параметрами, так как такие конструк­торы не принимают участия в неявных преобразованиях типа.

Соседние файлы в папке ЯзыкС++Глушаков