
- •Предисловие
- •Структура книги
- •Глава 1. Начинаем
- •1.1. Решение задачи
- •1.2. Программа на языке C++
- •1.2.1. Порядок выполнения инструкций
- •1.3. Директивы препроцессора
- •1.4. Немного о комментариях
- •1.5. Первый взгляд на ввод/вывод
- •1.5.1. Файловый ввод/вывод
- •Глава 2. Краткий обзор С++
- •2.1. Встроенный тип данных “массив”
- •2.2. Динамическое выделение памяти и указатели
- •2.3. Объектный подход
- •2.4. Объектно-ориентированный подход
- •2.5. Использование шаблонов
- •2.6. Использование исключений
- •2.7. Использование пространства имен
- •2.8. Стандартный массив – это вектор
- •Глава 3. Типы данных С++
- •3.1. Литералы
- •3.2. Переменные
- •3.2.1. Что такое переменная
- •3.2.2. Имя переменной
- •3.2.3. Определение объекта
- •3.3. Указатели
- •3.4. Строковые типы
- •3.4.1. Встроенный строковый тип
- •3.4.2. Класс string
- •3.5. Спецификатор const
- •3.6. Ссылочный тип
- •3.7. Тип bool
- •3.8. Перечисления
- •3.9. Тип “массив”
- •3.9.1. Многомерные массивы
- •3.9.2. Взаимосвязь массивов и указателей
- •3.10. Класс vector
- •3.13. Спецификатор volatile
- •3.14. Класс pair
- •3.15. Типы классов
- •Глава 4. Выражения
- •4.1. Что такое выражение?
- •4.3. Операции сравнения и логические операции
- •4.4. Операции присваивания
- •4.5. Операции инкремента и декремента
- •4.6. Операции с комплексными числами
- •4.7. Условное выражение
- •4.8. Оператор sizeof
- •4.9. Операторы new и delete
- •4.10. Оператор “запятая”
- •4.11. Побитовые операторы
- •4.12. Класс bitset
- •4.13. Приоритеты
- •4.14. Преобразования типов
- •4.14.1. Неявное преобразование типов
- •4.14.2. Арифметические преобразования типов
- •4.14.3. Явное преобразование типов
- •4.14.4. Устаревшая форма явного преобразования
- •4.15. Пример: реализация класса Stack
- •Глава 5. Инструкции
- •5.1. Простые и составные инструкции
- •5.2. Инструкции объявления
- •5.3. Инструкция if
- •5.4. Инструкция switch
- •5.5. Инструкция цикла for
- •5.6. Инструкция while
- •5.8. Инструкция do while
- •5.8. Инструкция break
- •5.9. Инструкция continue
- •5.10. Инструкция goto
- •5.11. Пример связанного списка
- •5.11.1. Обобщенный список
- •Глава 6. Абстрактные контейнерные типы
- •6.1. Система текстового поиска
- •6.2. Вектор или список?
- •6.3. Как растет вектор?
- •6.4. Как определить последовательный контейнер?
- •6.5. Итераторы
- •6.6. Операции с последовательными контейнерами
- •6.6.1. Удаление
- •6.6.2. Присваивание и обмен
- •6.6.3. Обобщенные алгоритмы
- •6.7. Читаем текстовый файл
- •6.8. Выделяем слова в строке
- •6.9. Обрабатываем знаки препинания
- •6.10. Приводим слова к стандартной форме
- •6.11. Дополнительные операции со строками
- •6.12. Строим отображение позиций слов
- •6.12.2. Поиск и извлечение элемента отображения
- •6.12.3. Навигация по элементам отображения
- •6.12.4. Словарь
- •6.12.5. Удаление элементов map
- •6.13. Построение набора стоп-слов
- •6.13.2. Поиск элемента
- •6.13.3. Навигация по множеству
- •6.14. Окончательная программа
- •6.15. Контейнеры multimap и multiset
- •6.16. Стек
- •6.17. Очередь и очередь с приоритетами
- •6.18. Вернемся в классу iStack
- •Глава 7. Функции
- •7.1. Введение
- •7.2. Прототип функции
- •7.2.1. Тип возвращаемого функцией значения
- •7.2.2. Список параметров функции
- •7.2.3. Проверка типов формальных параметров
- •7.3. Передача аргументов
- •7.3.1. Параметры-ссылки
- •7.3.2. Параметры-ссылки и параметры-указатели
- •7.3.3. Параметры-массивы
- •7.3.5. Значения параметров по умолчанию
- •7.3.6. Многоточие
- •7.4. Возврат значения
- •7.5. Рекурсия
- •7.6. Встроенные функции
- •7.8.1. Класс для обработки параметров командной строки
- •7.9. Указатели на функции
- •7.9.1. Тип указателя на функцию
- •7.9.2. Инициализация и присваивание
- •7.9.3. Вызов
- •7.9.4. Массивы указателей на функции
- •7.9.5. Параметры и тип возврата
- •Глава 8. Область видимости и время жизни
- •8.1. Область видимости
- •8.1.1. Локальная область видимости
- •8.2. Глобальные объекты и функции
- •8.2.1. Объявления и определения
- •8.2.2. Сопоставление объявлений в разных файлах
- •8.2.3. Несколько слов о заголовочных файлах
- •8.3.1. Автоматические объекты
- •8.3.2. Регистровые автоматические объекты
- •8.3.3. Статические локальные объекты
- •8.4. Динамически размещаемые объекты
- •8.4.3. Динамическое создание и уничтожение массивов
- •8.5.1. Определения пространства имен
- •8.5.2. Оператор разрешения области видимости
- •8.5.3. Вложенные пространства имен
- •8.5.4. Определение члена пространства имен
- •8.5.6. Безымянные пространства имен
- •8.6.1. Псевдонимы пространства имен
- •8.6.2. Using-объявления
- •8.6.3. Using-директивы
- •8.6.4. Стандартное пространство имен std
- •Глава 9. Перегруженные функции
- •9.1. Объявления перегруженных функций
- •9.1.1. Зачем нужно перегружать имя функции
- •9.1.2. Как перегрузить имя функции
- •9.1.3. Когда не надо перегружать имя функции
- •9.2. Три шага разрешения перегрузки
- •9.3.1. Подробнее о точном соответствии
- •9.3.2. Подробнее о расширении типов
- •9.3.3. Подробнее о стандартном преобразовании
- •9.3.4. Ссылки
- •9.4. Детали разрешения перегрузки функций
- •9.4.1. Функции-кандидаты
- •9.4.2. Устоявшие функции
- •9.4.3. Наилучшая из устоявших функция
- •9.4.4. Аргументы со значениями по умолчанию
- •Глава 10. Шаблоны функций
- •10.1. Определение шаблона функции
- •10.2. Конкретизация шаблона функции
- •10.5.1. Модель компиляции с включением
- •10.5.2. Модель компиляции с разделением
- •10.5.3. Явные объявления конкретизации
- •10.11. Пример шаблона функции
- •Глава 11. Обработка исключений
- •11.1. Возбуждение исключения
- •11.2. try-блок
- •11.3. Перехват исключений
- •11.3.1. Объекты-исключения
- •11.3.2. Раскрутка стека
- •11.3.3. Повторное возбуждение исключения
- •11.3.4. Перехват всех исключений
- •11.4. Спецификации исключений
- •11.4.1. Спецификации исключений и указатели на функции
- •11.5. Исключения и вопросы проектирования
- •Глава 12. Обобщенные алгоритмы
- •12.1. Краткий обзор
- •12.2. Использование обобщенных алгоритмов
- •12.3. Объекты-функции
- •12.3.1. Предопределенные объекты-функции
- •12.3.3. Сравнительные объекты-функции
- •12.3.4. Логические объекты-функции
- •12.3.5. Адаптеры функций для объектов-функций
- •12.3.6. Реализация объекта-функции
- •12.4. Еще раз об итераторах
- •12.4.1. Итераторы вставки
- •12.4.2. Обратные итераторы
- •12.4.3. Потоковые итераторы
- •12.4.4. Итератор istream_iterator
- •12.4.5. Итератор ostream_iterator
- •12.4.6. Пять категорий итераторов
- •12.5.1. Алгоритмы поиска
- •12.5.2. Алгоритмы сортировки и упорядочения
- •12.5.3. Алгоритмы удаления и подстановки
- •12.5.4. Алгоритмы перестановки
- •12.5.5. Численные алгоритмы
- •12.5.6. Алгоритмы генерирования и модификации
- •12.5.8. Алгоритмы работы с множествами
- •12.5.9. Алгоритмы работы с хипом
- •12.6.1. Операция list_merge()
- •12.6.2. Операция list::remove()
- •12.6.3. Операция list::remove_if()
- •12.6.5. Операция list::sort()
- •12.6.6. Операция list::splice()
- •12.6.7. Операция list::unique()
- •Глава 13. Классы
- •13.1. Определение класса
- •13.1.1. Данные-члены
- •13.1.2. Функции-члены
- •13.1.3. Доступ к членам
- •13.1.4. Друзья
- •13.1.5. Объявление и определение класса
- •13.3. Функции-члены класса
- •13.3.1. Когда использовать встроенные функции-члены
- •13.3.2. Доступ к членам класса
- •13.3.3. Закрытые и открытые функции-члены
- •13.3.4. Специальные функции-члены
- •13.3.5. Функции-члены со спецификаторами const и volatile
- •13.3.6. Объявление mutable
- •13.4. Неявный указатель this
- •13.4.1. Когда использовать указатель this
- •13.5. Статические члены класса
- •13.5.1. Статические функции-члены
- •13.6. Указатель на член класса
- •13.6.1. Тип члена класса
- •13.6.2. Работа с указателями на члены класса
- •13.6.3. Указатели на статические члены класса
- •13.7. Объединение – класс, экономящий память
- •13.8. Битовое поле – член, экономящий память
- •13.9.1. Разрешение имен в области видимости класса
- •Глава 14. Инициализация, присваивание и уничтожение класса
- •14.1. Инициализация класса
- •14.2. Конструктор класса
- •14.2.1. Конструктор по умолчанию
- •14.2.2. Ограничение прав на создание объекта
- •14.2.3. Копирующий конструктор
- •14.3. Деструктор класса
- •14.3.1. Явный вызов деструктора
- •14.3.2. Опасность увеличения размера программы
- •14.4. Массивы и векторы объектов
- •14.4.2. Вектор объектов
- •14.5. Список инициализации членов
- •14.6.1. Инициализация члена, являющегося объектом класса
- •Глава 15. Перегруженные операторы и определенные пользователем преобразования
- •15.1. Перегрузка операторов
- •15.1.1. Члены и не члены класса
- •15.1.2. Имена перегруженных операторов
- •15.1.3. Разработка перегруженных операторов
- •15.2. Друзья
- •15.3. Оператор =
- •15.4. Оператор взятия индекса
- •15.5. Оператор вызова функции
- •15.6. Оператор “стрелка”
- •15.7. Операторы инкремента и декремента
- •15.8. Операторы new и delete
- •15.8.1. Операторы new[ ] и delete [ ]
- •15.8.2. Оператор размещения new() и оператор delete()
- •15.9. Определенные пользователем преобразования
- •15.9.1. Конвертеры
- •15.9.2. Конструктор как конвертер
- •15.10.1. Еще раз о разрешении перегрузки функций
- •15.10.2. Функции-кандидаты
- •15.11.1. Объявления перегруженных функций-членов
- •15.11.2. Функции-кандидаты
- •15.11.3. Устоявшие функции
- •15.12.1. Операторные функции-кандидаты
- •15.12.2. Устоявшие функции
- •15.12.3. Неоднозначность
- •Глава 16. Шаблоны классов
- •16.1. Определение шаблона класса
- •16.1.1. Определения шаблонов классов Queue и QueueItem
- •16.2. Конкретизация шаблона класса
- •16.2.1. Аргументы шаблона для параметров-констант
- •16.3. Функции-члены шаблонов классов
- •16.3.1. Функции-члены шаблонов Queue и QueueItem
- •16.4. Объявления друзей в шаблонах классов
- •16.4.1. Объявления друзей в шаблонах Queue и QueueItem
- •16.5. Статические члены шаблонов класса
- •16.6. Вложенные типы шаблонов классов
- •16.7. Шаблоны-члены
- •16.8.2. Модель компиляции с разделением
- •16.8.3. Явные объявления конкретизации
- •16.12. Пространства имен и шаблоны классов
- •16.13. Шаблон класса Array
- •Глава 17. Наследование и подтипизация классов
- •17.1. Определение иерархии классов
- •17.1.1. Объектно-ориентированное проектирование
- •17.2. Идентификация членов иерархии
- •17.2.1. Определение базового класса
- •17.2.3. Резюме
- •17.3. Доступ к членам базового класса
- •17.4. Конструирование базового и производного классов
- •17.4.1. Конструктор базового класса
- •17.4.2. Конструктор производного класса
- •17.4.3. Альтернативная иерархия классов
- •17.4.4. Отложенное обнаружение ошибок
- •17.4.5. Деструкторы
- •17.5.1. Виртуальный ввод/вывод
- •17.5.2. Чисто виртуальные функции
- •17.5.3. Статический вызов виртуальной функции
- •17.5.4. Виртуальные функции и аргументы по умолчанию
- •17.5.5. Виртуальные деструкторы
- •17.5.6. Виртуальная функция eval()
- •17.5.7. Почти виртуальный оператор new
- •17.5.8. Виртуальные функции, конструкторы и деструкторы
- •17.7. Управляющий класс UserQuery
- •17.7.1. Определение класса UserQuery
- •17.8. Соберем все вместе
- •Глава 18. Множественное и виртуальное наследование
- •18.1. Готовим сцену
- •18.2. Множественное наследование
- •18.3. Открытое, закрытое и защищенное наследование
- •18.3.1. Наследование и композиция
- •18.3.2. Открытие отдельных членов
- •18.3.3. Защищенное наследование
- •18.3.4. Композиция объектов
- •18.4. Область видимости класса и наследование
- •18.5.1. Объявление виртуального базового класса
- •18.5.2. Специальная семантика инициализации
- •18.5.3. Порядок вызова конструкторов и деструкторов
- •18.5.4. Видимость членов виртуального базового класса
- •18.6.2. Порождение класса отсортированного массива
- •18.6.3. Класс массива с множественным наследованием
- •19.1. Идентификация типов во время выполнения
- •19.1.1. Оператор dynamic_cast
- •19.1.2. Оператор typeid
- •19.1.3. Класс type_info
- •19.2. Исключения и наследование
- •19.2.1. Исключения, определенные как иерархии классов
- •19.2.2. Возбуждение исключения типа класса
- •19.2.4. Объекты-исключения и виртуальные функции
- •19.2.5. Раскрутка стека и вызов деструкторов
- •19.2.6. Спецификации исключений
- •19.2.7. Конструкторы и функциональные try-блоки
- •19.3.1. Функции-кандидаты
- •19.3.3. Наилучшая из устоявших функций
- •Глава 20. Библиотека IOSTREAM
- •20.1. Оператор вывода <<
- •20.2. Ввод
- •20.2.1. Строковый ввод
- •20.3. Дополнительные операторы ввода/вывода
- •20.4. Перегрузка оператора вывода
- •20.5. Перегрузка оператора ввода
- •20.6. Файловый ввод/вывод
- •20.7. Состояния потока
- •20.8. Строковые потоки
- •20.9. Состояние формата
- •20.10. Сильно типизированная библиотека
- •Приложение
- •Глава 21. Обобщенные алгоритмы в алфавитном порядке
- •accumulate()
- •adjacent_difference()
- •adjacent_find()
- •binary_search()
- •copy()
- •copy_backward()
- •count_if()
- •equal()
- •equal_range()
- •fill()
- •find()
- •find_if()
- •find_end()
- •find_first_of()
- •generate()
- •generate_n()
- •includes()
- •inplace_merge()
- •iter_swap()
- •lexicographical_compare()
- •max_element()
- •merge()
- •mismatch()
- •next_permutation()
- •nth_element()
- •partial_sort()
- •partial_sort_copy()
- •partial_sum()
- •partition()
- •prev_permutation()
- •random_shuffle()
- •remove()
- •remove_copy()
- •remove_if()
- •remove_copy_if()
- •replace()
- •replace_copy()
- •replace_if()
- •replace_copy_if()
- •reverse_copy()
- •rotate()
- •search_n()
- •set_intersection()
- •set_union()
- •sort()
- •stable_partition()
- •swap()
- •swap_ranges()
- •transform()
- •unique_copy()
- •upper_bound()
- •Алгоритмы для работы с хипом
- •make_heap()
- •pop_heap()
- •push_heap()
- •sort_heap()

С++ для начинающих |
701 |
inline Account::
Account( const Account &rhs )
{
//решить проблему псевдонима указателя
_name = new char[ strlen(rhs._name)+1 ]; strcpy( _name, rhs._name );
//решить проблему уникальности номера счета
_acct_nmbr = get_unique_acct_nmbr();
//копирование этого члена и так работает
_balance = rhs._balance;
}
Альтернативой написанию копирующего конструктора является полный запрет почленной инициализации. Это можно сделать следующим образом:
1.Объявить копирующий конструктор закрытым членом. Это предотвратит почленную инициализацию всюду, кроме функций-членов и друзей класса.
2.Запретить почленную инициализацию в функциях-членах и друзьях класса, намеренно не предоставляя определения копирующего конструктора (однако объявить его так, как описано на шаге 1, все равно нужно). Язык не дает нам возможности ограничить доступ к закрытым членам класса со стороны функцийчленов и друзей. Но если определение отсутствует, то любая попытка вызвать копирующий конструктор, законная с точки зрения компилятора, приведет к ошибке во время редактирования связей, поскольку не удастся найти определение символа.
class Account { public:
Account();
Account( const char*, double=0.0 );
//...
private:
Account( const Account& );
//...
Чтобы запретить почленную инициализацию, класс Account можно объявить так:
};
14.6.1. Инициализация члена, являющегося объектом класса
Что произойдет, если в объявлении _name заменить C-строку на тип класса string? Как это повлияет на почленную инициализацию по умолчанию? Как надо будет изменить явный копирующий конструктор? Мы ответим на эти вопросы в данном подразделе.
При почленной инициализации по умолчанию исследуется каждый член. Если он принадлежит к встроенному или составному типу, то такая инициализация применяется непосредственно. Например, в первоначальном определении класса Account член _name инициализируется непосредственно, так как это указатель:
newAcct._name = oldAcct._name;

С++ для начинающих |
702 |
Члены, являющиеся объектами классов, обрабатываются по-другому. В инструкции
Account newAcct( oldAcct );
оба объекта распознаются как экземпляры Account. Если у этого класса есть явный копирующий конструктор, то он и применяется для задания начального значения, в противном случае выполняется почленная инициализация по умолчанию.
Таким образом, если обнаруживается член-объект класса, то описанный выше процесс применяется рекурсивно. У класса есть явный копирующий конструктор? Если да, вызвать его для задания начального значения члена-объекта класса. Иначе применить к этому члену почленную инициализацию по умолчанию. Если все члены этого класса принадлежат к встроенным или составным типам, то каждый инициализируется непосредственно и процесс на этом завершается. Если же некоторые члены сами являются объектами классов, то алгоритм применяется к ним рекурсивно, пока не останется ничего, кроме встроенных и составных типов.
В нашем примере у класса string есть явный копирующий конструктор, поэтому _name инициализируется с помощью его вызова. Копирующий конструктор по умолчанию для
inline Account::
Account( const Account &rhs )
{
_acct_nmbr = rhs._acct_nmbr; _balance = rhs._balance;
//Псевдокод на C++
//иллюстрирует вызов копирующего конструктора
//для члена, являющегося объектом класса
_name.string::string( rhs._name );
класса Account выглядит следующим образом (хотя явно он не определен):
}
Теперь почленная инициализация по умолчанию для класса Account корректно обрабатывает выделение и освобождение памяти для _name, но все еще неверно копирует номер счета, поэтому приходится кодировать явный копирующий конструктор. Однако
// не совсем правильно...
inline Account::
Account( const Account &rhs )
{
_name = rhs._name; _balance = rhs._balance;
_acct_nmbr = get_unique_acct_nmbr();
приведенный ниже фрагмент не совсем правилен. Можете ли вы сказать, почему?
}
Эта реализация ошибочна, поскольку в ней не различаются инициализация и присваивание. В результате вместо вызова копирующего конструктора string мы вызываем конструктор string по умолчанию на фазе неявной инициализации и копирующий оператор присваивания string – в теле конструктора. Исправить это несложно:

С++ для начинающих |
703 |
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;
};
};

С++ для начинающих |
704 |
(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;
почленная инициализация:
}
14.7. Почленное присваивание A
Присваивание одному объекту класса значения другого объекта того же класса реализуется почленным присваиванием по умолчанию. От почленной инициализации по умолчанию оно отличается только использованием копирующего оператора присваивания вместо копирующего конструктора:
newAcct = oldAcct;

С++ для начинающих |
705 |
по умолчанию присваивает каждому нестатическому члену 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:

С++ для начинающих |
706 |
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 );
присваивания:
}

С++ для начинающих |
707 |
Однако почленное присваивание по умолчанию для объектов класса Account не подходит из-за _acct_nmbr. Нужно реализовать явный копирующий оператор
Account&
Account::
operator=( const Account &rhs )
{
// не надо присваивать самому себе if ( this != &rhs )
{
// вызывается string::operator=( const string& ) _name = rhs._name;
_balance = rhs._balance;
}
return *this;
присваивания с учетом того, что _name – это объект класса string:
}
Чтобы запретить почленное копирование, мы поступаем так же, как и в случае почленной инициализации: объявляем оператор закрытым и не предоставляем его определения.
Копирующий конструктор и копирующий оператор присваивания обычно рассматривают вместе. Если необходим один, то, как правило, необходим и другой. Если запрещается один, то, вероятно, следует запретить и другой.
Упражнение 14.17
Реализуйте копирующий оператор присваивания для каждого из классов, определенных в упражнении 14.14 из раздела 14.6.
Упражнение 14.18
Нужен ли копирующий оператор присваивания для того класса, который вы выбрали в упражнении 14.3 из раздела 14.2? Если да, реализуйте его. В противном случае объясните, почему он не нужен.
14.8. Соображения эффективности A
В общем случае объект класса эффективнее передавать функции по указателю или по ссылке, нежели по значению. Например, если дана функция с сигнатурой:
bool sufficient_funds( Account acct, double );
то при каждом ее вызове требуется выполнить почленную инициализацию формального параметра acct значением фактического аргумента-объекта класса Account. Если же
bool sufficient_funds( Account *pacct, double );
функция имеет любую из таких сигнатур:
bool sufficient_funds( Account &acct, double );

С++ для начинающих |
708 |
то достаточно скопировать адрес объекта Account. В этом случае никакой инициализации класса не происходит (см. обсуждение взаимосвязи между ссылочными и указательными параметрами в разделе 7.3).
Хотя возвращать указатель или ссылку на объект класса также более эффективно, чем сам объект, но корректно запрограммировать это достаточно сложно. Рассмотрим такой
//задача решается, но для больших матриц эффективность может
//оказаться неприемлемо низкой
Matrix
operator+( const Matrix& m1, const Matrix& m2 )
{
Matrix result;
// выполнить арифметические операции ...
return result;
оператор сложения:
}
Matrix a, b;
//...
//в обоих случаях вызывается operator+() Matrix c = a + b;
Этот перегруженный оператор позволяет пользователю писать
a = b + c;
Однако возврат результата по значению может потребовать слишком больших затрат времени и памяти, если Matrix представляет собой большой и сложный класс. Если эта операция выполняется часто, то она, вероятно, резко снизит производительность.
//более эффективно, но после возврата адрес оказывается недействительным
//это может привести к краху программы
Matrix&
operator+( const Matrix& m1, const Matrix& m2 )
{
Matrix result;
// выполнить сложение ...
return result;
Следующая пересмотренная реализация намного увеличивает скорость:
}
но при этом происходят частые сбои программы. Дело в том, что значение переменной result не определено после выхода из функции, в которой она объявлена. (Мы возвращаем ссылку на локальный объект, который после возврата не существует.)
Значение возвращаемого адреса должно оставаться действительным после выхода из функции. В приведенной реализации возвращаемый адрес не затирается:

С++ для начинающих |
709 |
//нет возможности гарантировать отсутствие утечки памяти
//поскольку матрица может быть большой, утечки будут весьма заметными
Matrix&
operator+( const Matrix& m1, const Matrix& m2 )
{
Matrix *result = new Matrix; // выполнить сложение ...
return *result;
}
Однако это неприемлемо: происходит большая утечка памяти, так как ни одна из частей программы не отвечает за применение оператора delete к объекту по окончании его использования.
Вместо оператора сложения лучше применять именованную функцию, которой в качестве
//это обеспечивает нужную эффективность,
//но не является интуитивно понятным для пользователя
void
mat_add( Matrix &result,
const Matrix& m1, const Matrix& m3 )
{
// вычислить результат
третьего параметра передается ссылка, где следует сохранить результат:
}
Таким образом, проблема производительности решается, но для класса уже нельзя использовать операторный синтаксис, так что теряется возможность инициализировать
// более не поддерживается
объекты
Matrix c = a + b;
// тоже не поддерживается
ииспользовать их в выражениях: if ( a + b > c ) ...
Неэффективный возврат объекта класса – слабое место С++. В качестве одного из решений предлагалось расширить язык, введя имя возвращаемого функцией объекта:

С++ для начинающих |
710 |
Matrix&
operator+( const Matrix& m1, const Matrix& m2 ) name result
{
Matrix result; // ...
return result;
}
Тогда компилятор мог бы самостоятельно переписать функцию, добавив к ней третий
//переписанная компилятором функция
//в случае принятия предлагавшегося расширения языка
void
operator+( Matrix &result, const Matrix& m1, const Matrix& m2 ) name result
{
// вычислить результат
параметр-ссылку:
}
и преобразовать все вызовы этой функции, разместив результат непосредственно в области, на которую ссылается первый параметр. Например:
Matrix c = a + b;
Matrix c;
было бы трансформировано в
operator+(c, a, b);
Это расширение так и не стало частью языка, но предложенная оптимизация прижилась. Компилятор в состоянии распознать, что возвращается объект класса и выполнить трансформацию его значения и без явного расширения языка. Если дана функция общего
classType
functionName( paramList )
{
classType namedResult;
// выполнить какие-то действия ...
return namedResult;
вида:
}
то компилятор самостоятельно трансформирует как саму функцию, так и все обращения к ней:

С++ для начинающих |
711 |
void
functionName( classType &namedResult, paramList )
{
// вычислить результат и разместить его по адресу namedResult
}
что позволяет уйти от необходимости возвращать значение объекта и вызывать копирующий конструктор. Чтобы такая оптимизация была применена, в каждой точке возврата из функции должен возвращаться один и тот же именованный объект класса.
И последнее замечание об эффективности работы с объектами в C++. Инициализация объекта класса вида
Matrix c = a + b;
всегда эффективнее присваивания. Например, результат следующих двух инструкций
Matrix c;
такой же, как и в предыдущем случае:
c = a + b;
for ( int ix = 0; ix < size-2; ++ix ) { Matrix matSum = mat[ix] + mat[ix+1]; // ...
но объем требуемых вычислений значительно больше. Аналогично эффективнее писать:
}
Matrix matSum;
for ( int ix = 0; ix < size-2; ++ix ) { matSum = mat[ix] + mat[ix+1];
// ...
чем
}
Причина, по которой присваивание всегда менее эффективно, состоит в том, что возвращенный локальный объект нельзя подставить вместо объекта в левой части оператора присваивания. Иными словами, в то время как инструкцию
Point3d p3 = operator+( p1, p2 );
можно безопасно трансформировать:

С++ для начинающих |
712 |
// Псевдокод на C++ Point3d p3;
operator+( p3, p1, p2 );
Point3d p3;
преобразование
p3 = operator+( p1, p2 );
//Псевдокод на C++
//небезопасно в случае присваивания
в
operator+( p3, p1, p2 );
небезопасно.
Преобразованная функция требует, чтобы переданный ей объект представлял собой неформатированную область памяти. Почему? Потому что к объекту сразу применяется конструктор, который уже был применен к именованному локальному объекту. Если переданный объект уже был сконструирован, то делать это еще раз с семантической точки зрения неверно.
Что касается инициализируемого объекта, то отведенная под него память еще не подвергалась обработке. Если же объекту присваивается значение и в классе объявлены конструкторы (а именно этот случай мы и рассматриваем), можно утверждать, что эта память уже форматировалась одним из них, так что непосредственно передавать объект функции небезопасно.
Вместо этого компилятор должен создать неформатированную область памяти в виде временного объекта класса, передать его функции, а затем почленно присвоить возвращенный временный объект объекту, стоящему в левой части оператора присваивания. Наконец, если у класса есть деструктор, то он применяется к временному
Point3d p3;
объекту. Например, следующий фрагмент
p3 = operator+( p1, p2 );
// Псевдокод на C++ Point3d temp;
operator+( temp, p1, p2 ); p3.Point3d::operator=( temp );
трансформируется в такой:
temp.Point3d::~Point3d();

С++ для начинающих |
713 |
Майкл Тиманн (Michael Tiemann), автор компилятора GNU C++, предложил назвать это расширение языка именованным возвращаемым значением (return value language extension). Его точка зрения изложена в работе [LIPPMAN96b]. В нашей книге “Inside the C++ Object Model” ([LIPPMAN96a]) приводится детальное обсуждение затронутых в этой главе тем.