Язык программирования C++

         

Копирующий конструктор


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

class String { public: String(const String s); }; String::String(const String s) { length = s.length; str = new char[length + 1]; strcpy(str, s.str); }

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

// первый объект с начальным значением // "Astring" String a("Astring"); // новый объект – копия первого, // т.е. со значением "Astring" String b(a); // изменение значения b на "AstringAstring", // значение объекта a не изменяется b.Concat(a);

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

String::String(const String s) { length = s.length; str = s.str; }

При вызове метода Concat для объекта b произошло бы следующее: объект b перераспределил бы память под строку str, выделив новый участок памяти и удалив предыдущий (см. определение метода выше). Однако указатель str объекта a по-прежнему указывает на первоначальный участок памяти, только что освобожденный объектом b. Соответственно, значение объекта a испорчено.

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

class Complex { public: Complex(); Complex(int rl, int im = 0); Complex(const Complex c); // прибавить комплексное число Complex operator+(const Complex x) const; private: int real; // вещественная часть int imaginary; // мнимая часть


}; // // Стандартный конструктор создает число (0,0) // Complex::Complex() : real(0), imaginary(0) {} // // Создать комплексное число из действительной // и мнимой частей. У второго аргумента есть // значение по умолчанию — мнимая часть равна // нулю Complex::Complex(int rl, int im) : real(rl), imaginary(im) {} // // Скопировать значение комплексного числа // Complex::Complex(const Complex c) : real(c.real), imaginary(c.imaginary) {}

Теперь при создании комплексных чисел происходит их инициализация:

Complex x1; // начальное значение – ноль Complex x2(3); // мнимая часть по умолчанию равна 0 // создается действительное число 3 Complex x3(0, 1); // мнимая единица Complex y(x3); // мнимая единица

Конструкторы, особенно копирующие, довольно часто выполняются неявно. Предположим, мы бы описали метод Concat несколько иначе:

Concat(String s);

вместо

Concat(const String s);

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

b.Concat(a)

компилятор создал бы временную переменную типа String – копию объекта a, и передал бы ее в качестве аргумента. При выходе из метода String эта переменная была бы уничтожена. Представляете, насколько снизилось бы быстродействие метода!

Второй пример вызова конструктора – неявное преобразование типа. Допустима запись вида:

b.Concat("LITERAL");

хотя сам метод определен только для аргумента – объекта типа String. Поскольку в классе String есть конструктор с аргументом – указателем на байт (а литерал – как раз константа такого типа), компилятор произведет автоматическое преобразование. Будет создана автоматическая переменная типа String с начальным значением "LITERAL", ссылка на нее будет передана в качестве аргумента метода String, а по завершении Concat временная переменная будет уничтожена.

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



class String { public: void Concat(const String s); void Concat(const char* s); }; void String::Concat(const char* s) { length += strlen(s); char* tmp = new char[length + 1]; if (tmp == 0) { // обработка ошибки } strcpy(tmp, str); strcat(tmp, s); delete [] str; str = tmp; }

Содержание раздела