Сделай Сам Свою Работу на 5

Инициализация члена, являющегося объектом класса

Что произойдет, если в объявлении _name заменить C-строку на тип класса string? Как это повлияет на почленную инициализацию по умолчанию? Как надо будет изменить явный копирующий конструктор? Мы ответим на эти вопросы в данном подразделе.

При почленной инициализации по умолчанию исследуется каждый член. Если он принадлежит к встроенному или составному типу, то такая инициализация применяется непосредственно. Например, в первоначальном определении класса Account член _name инициализируется непосредственно, так как это указатель:

newAcct._name = oldAcct._name;

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

Account newAcct( oldAcct );

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

Таким образом, если обнаруживается член-объект класса, то описанный выше процесс применяется рекурсивно. У класса есть явный копирующий конструктор? Если да, вызвать его для задания начального значения члена-объекта класса. Иначе применить к этому члену почленную инициализацию по умолчанию. Если все члены этого класса принадлежат к встроенным или составным типам, то каждый инициализируется непосредственно и процесс на этом завершается. Если же некоторые члены сами являются объектами классов, то алгоритм применяется к ним рекурсивно, пока не останется ничего, кроме встроенных и составных типов.

В нашем примере у класса string есть явный копирующий конструктор, поэтому _name инициализируется с помощью его вызова. Копирующий конструктор по умолчанию для класса Account выглядит следующим образом (хотя явно он не определен):

inline Account:: Account( const Account &rhs ) { _acct_nmbr = rhs._acct_nmbr; _balance = rhs._balance;   // Псевдокод на C++ // иллюстрирует вызов копирующего конструктора // для члена, являющегося объектом класса _name.string::string( rhs._name );

}

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



// не совсем правильно... inline Account:: Account( const Account &rhs ) { _name = rhs._name; _balance = rhs._balance; _acct_nmbr = get_unique_acct_nmbr();

}

Эта реализация ошибочна, поскольку в ней не различаются инициализация и присваивание. В результате вместо вызова копирующего конструктора string мы вызываем конструктор string по умолчанию на фазе неявной инициализации и копирующий оператор присваивания string – в теле конструктора. Исправить это несложно:

inline Account:: Account( const Account &rhs ) : _name( rhs._name ) { _balance = rhs._balance; _acct_nmbr = get_unique_acct_nmbr();

}

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

Упражнение 14.13

Для какого определения класса скорее всего понадобится копирующий конструктор?

1. Представление Point3w, содержащее четыре числа с плавающей точкой.

2. Класс matrix, в котором память для хранения матрицы выделяется динамически в конструкторе и освобождается в деструкторе.

3. Класс payroll (платежная ведомость), где каждому объекту приписывается уникальный идентификатор.

4. Класс word (слово), содержащий объект класса string и вектор, в котором хранятся пары (номер строки, смещение в строке).

Упражнение 14.14

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

(a) class BinStrTreeNode { public: // ... private: string _value; int _count; BinStrTreeNode *_leftchild; BinStrTreeNode *_rightchild;

};

(b) class BinStrTree { public: // ... private: BinStrTreeNode *_root;

};

(c) class iMatrix { public: // ... private: int _rows; int _cols; int *_matrix;

};

(d) class theBigMix { public: // ... private: BinStrTree _bst; iMatrix _im; string _name; vectorMfloat> *_pvec;

};

Упражнение 14.15

Нужен ли копирующий конструктор для того класса, который вы выбрали в упражнении 14.3 из раздела 14.2? Если нет, объясните почему. Если да, реализуйте его.

Упражнение 14.16

Идентифицируйте в следующем фрагменте программы все места, где происходит почленная инициализация:

Point global;   Point foo_bar( Point arg ) { Point local = arg; Point *heap = new Point( global ); *heap = local; Point pa[ 4 ] = { local, *heap }; return *heap;

}

Почленное присваивание A

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

newAcct = oldAcct;

по умолчанию присваивает каждому нестатическому члену newAcct значение соответственного члена oldAcct. Компилятор генерирует следующий копирующий оператор присваивания:

inline Account& Account:: operator=( const Account &rhs ) { _name = rhs._name; _balance = rhs._balance; _acct_nmbr = rhs._acct_nmbr;

}

Как правило, если для класса не подходит почленная инициализация по умолчанию, то не подходит и почленное присваивание по умолчанию. Например, для первоначального определения класса Account, где член _name был объявлен как char*, такое присваивание не годится ни для _name, ни для _acct_nmbr.

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

// общий вид копирующего оператора присваивания className& className:: operator=( const className &rhs ) { // не надо присваивать самому себе if ( this != &rhs ) { // здесь реализуется семантика копирования класса } // вернуть объект, которому присвоено значение return *this;

}

Здесь условная инструкция

if ( this != &rhs )

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

Account& Account:: operator=( const Account &rhs ) { // не надо присваивать самому себе if ( this != &rhs ) { delete [] _name; _name = new char[strlen(rhs._name)+1]; strcpy( _name,rhs._name ); _balance = rhs._balance; _acct_nmbr = rhs._acct_nmbr; } return *this;

}

Когда один объект класса присваивается другому, как, например, в инструкции:

newAcct = oldAcct;

выполняются следующие шаги:

1. Выясняется, есть ли в классе явный копирующий оператор присваивания.

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

3. Оператор вызывается для выполнения присваивания; если же он недоступен, компилятор выдает сообщение об ошибке.

4. Если явного оператора нет, выполняется почленное присваивание по умолчанию.

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

6. Для каждого члена, являющегося объектом класса, рекурсивно применяются шаги 1-6, пока не останутся только члены встроенных и составных типов.

Если мы снова модифицируем определение класса Account так, что _name будет иметь тип string, то почленное присваивание по умолчанию

newAcct = oldAcct;

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

inline Account& Account:: operator=( const Account &rhs ) { _balance = rhs._balance; _acct_nmbr = rhs._acct_nmbr;   // этот вызов правилен и с точки зрения программиста name.string::operator=( rhs._name );

}

Однако почленное присваивание по умолчанию для объектов класса Account не подходит из-за _acct_nmbr. Нужно реализовать явный копирующий оператор присваивания с учетом того, что _name – это объект класса string:

Account& Account:: operator=( const Account &rhs ) { // не надо присваивать самому себе if ( this != &rhs ) { // вызывается string::operator=( const string& ) _name = rhs._name; _balance = rhs._balance; } return *this;

}

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

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

Упражнение 14.17

Реализуйте копирующий оператор присваивания для каждого из классов, определенных в упражнении 14.14 из раздела 14.6.

Упражнение 14.18

Нужен ли копирующий оператор присваивания для того класса, который вы выбрали в упражнении 14.3 из раздела 14.2? Если да, реализуйте его. В противном случае объясните, почему он не нужен.



©2015- 2019 stydopedia.ru Все материалы защищены законодательством РФ.