С++ для начинающих

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


Что произойдет, если в объявлении _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;

}


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