Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Инициализация члена, являющегося объектом класса
Что произойдет, если в объявлении _name заменить C-строку на тип класса string? Как это повлияет на почленную инициализацию по умолчанию? Как надо будет изменить явный копирующий конструктор? Мы ответим на эти вопросы в данном подразделе. При почленной инициализации по умолчанию исследуется каждый член. Если он принадлежит к встроенному или составному типу, то такая инициализация применяется непосредственно. Например, в первоначальном определении класса Account член _name инициализируется непосредственно, так как это указатель: newAcct._name = oldAcct._name; Члены, являющиеся объектами классов, обрабатываются по-другому. В инструкции Account newAcct( oldAcct ); оба объекта распознаются как экземпляры Account. Если у этого класса есть явный копирующий конструктор, то он и применяется для задания начального значения, в противном случае выполняется почленная инициализация по умолчанию. Таким образом, если обнаруживается член-объект класса, то описанный выше процесс применяется рекурсивно. У класса есть явный копирующий конструктор? Если да, вызвать его для задания начального значения члена-объекта класса. Иначе применить к этому члену почленную инициализацию по умолчанию. Если все члены этого класса принадлежат к встроенным или составным типам, то каждый инициализируется непосредственно и процесс на этом завершается. Если же некоторые члены сами являются объектами классов, то алгоритм применяется к ним рекурсивно, пока не останется ничего, кроме встроенных и составных типов. В нашем примере у класса string есть явный копирующий конструктор, поэтому _name инициализируется с помощью его вызова. Копирующий конструктор по умолчанию для класса Account выглядит следующим образом (хотя явно он не определен):
} Теперь почленная инициализация по умолчанию для класса Account корректно обрабатывает выделение и освобождение памяти для _name, но все еще неверно копирует номер счета, поэтому приходится кодировать явный копирующий конструктор. Однако приведенный ниже фрагмент не совсем правилен. Можете ли вы сказать, почему?
} Эта реализация ошибочна, поскольку в ней не различаются инициализация и присваивание. В результате вместо вызова копирующего конструктора string мы вызываем конструктор string по умолчанию на фазе неявной инициализации и копирующий оператор присваивания string – в теле конструктора. Исправить это несложно:
} Самое главное – понять, что такое исправление необходимо. (Обе реализации приводят к тому, что в _name копируется значение из rhs._name, но в первой одна и та же работа выполняется дважды.) Общее эвристическое правило состоит в том, чтобы по возможности инициализировать все члены-объекты классов в списке инициализации членов. Упражнение 14.13 Для какого определения класса скорее всего понадобится копирующий конструктор? 1. Представление Point3w, содержащее четыре числа с плавающей точкой. 2. Класс matrix, в котором память для хранения матрицы выделяется динамически в конструкторе и освобождается в деструкторе. 3. Класс payroll (платежная ведомость), где каждому объекту приписывается уникальный идентификатор. 4. Класс word (слово), содержащий объект класса string и вектор, в котором хранятся пары (номер строки, смещение в строке). Упражнение 14.14 Реализуйте для каждого из данных классов копирующий конструктор, конструктор по умолчанию и деструктор.
};
};
};
}; Упражнение 14.15 Нужен ли копирующий конструктор для того класса, который вы выбрали в упражнении 14.3 из раздела 14.2? Если нет, объясните почему. Если да, реализуйте его. Упражнение 14.16 Идентифицируйте в следующем фрагменте программы все места, где происходит почленная инициализация:
} Почленное присваивание A Присваивание одному объекту класса значения другого объекта того же класса реализуется почленным присваиванием по умолчанию. От почленной инициализации по умолчанию оно отличается только использованием копирующего оператора присваивания вместо копирующего конструктора: newAcct = oldAcct; по умолчанию присваивает каждому нестатическому члену newAcct значение соответственного члена oldAcct. Компилятор генерирует следующий копирующий оператор присваивания:
} Как правило, если для класса не подходит почленная инициализация по умолчанию, то не подходит и почленное присваивание по умолчанию. Например, для первоначального определения класса Account, где член _name был объявлен как char*, такое присваивание не годится ни для _name, ни для _acct_nmbr. Мы можем подавить его, если предоставим явный копирующий оператор присваивания, где будет реализована подходящая для класса семантика:
} Здесь условная инструкция if ( this! = & rhs ) предотвращает присваивание объекта класса самому себе, что особенно неприятно в ситуации, когда копирующий оператор присваивания сначала освобождает некоторый ресурс, ассоциированный с объектом в левой части, чтобы назначить вместо него ресурс, ассоциированный с объектом в правой части. Рассмотрим копирующий оператор присваивания для класса Account:
} Когда один объект класса присваивается другому, как, например, в инструкции: newAcct = oldAcct; выполняются следующие шаги: 1. Выясняется, есть ли в классе явный копирующий оператор присваивания. 2. Если есть, проверяются права доступа к нему, чтобы понять, можно ли его вызывать в данном месте программы. 3. Оператор вызывается для выполнения присваивания; если же он недоступен, компилятор выдает сообщение об ошибке. 4. Если явного оператора нет, выполняется почленное присваивание по умолчанию. 5. При почленном присваивании каждому члену встроенного или составного члена объекта в левой части присваивается значение соответственного члена объекта в правой части. 6. Для каждого члена, являющегося объектом класса, рекурсивно применяются шаги 1-6, пока не останутся только члены встроенных и составных типов. Если мы снова модифицируем определение класса Account так, что _name будет иметь тип string, то почленное присваивание по умолчанию newAcct = oldAcct; будет выполняться так же, как при создании компилятором следующего оператора присваивания:
} Однако почленное присваивание по умолчанию для объектов класса Account не подходит из-за _acct_nmbr. Нужно реализовать явный копирующий оператор присваивания с учетом того, что _name – это объект класса string:
} Чтобы запретить почленное копирование, мы поступаем так же, как и в случае почленной инициализации: объявляем оператор закрытым и не предоставляем его определения. Копирующий конструктор и копирующий оператор присваивания обычно рассматривают вместе. Если необходим один, то, как правило, необходим и другой. Если запрещается один, то, вероятно, следует запретить и другой. Упражнение 14.17 Реализуйте копирующий оператор присваивания для каждого из классов, определенных в упражнении 14.14 из раздела 14.6. Упражнение 14.18 Нужен ли копирующий оператор присваивания для того класса, который вы выбрали в упражнении 14.3 из раздела 14.2? Если да, реализуйте его. В противном случае объясните, почему он не нужен. |
Последнее изменение этой страницы: 2019-04-09; Просмотров: 306; Нарушение авторского права страницы