Что такое абстрактная функция
Что такое абстрактная функция
Виртуальные функции – функции базового класса, которые можно заместить в каждом производном классе. Если базовый класс вызывает перегруженную виртуальную функцию, то всегда будет вызываться функция наследника.
Для того, чтобы сделать функцию виртуальной, при ее описании в базовом классе необходимо указать ключевое слово virtual
Реализация функции в базовом классе не изменится, а классе-наследнике и реализация и описание функции останется прежним.
Использование виртуальных функций важно, когда создается экземпляр указателя на класс-наследник, если при этом экземпляр описан как базовый класс, но создается как наследник.
Делать виртуальными можно практически все функции, за исключением конструкторов и оператора =. Особый интерес представляют виртуальные деструкторы. Виртуальным деструктор делают, если для правильного осовобождения памяти необходимо, чтобы деструктор всегда вызывался для класса-наследника.
5.2.2. Абстрактные классы
Некоторые базовые классы (например, класс Detail) представляют собой абстрактную концепцию, для которой не могут существовать экземпляры. Невозможно нарисовать абстрактную деталь или выполнить расчет прохождения луча через деталь.
В таких случаях базовый класс делают абстрактным, то есть классом, экземпляр которого создать нельзя, но можно создать экземпляры его наследников.
Абстрактным называется класс, имеющий чисто виртуальные функции. Чисто виртуальная функция – это функция, которая не определена в базовом классе, и обязательно должна быть перегружена в классах наследниках. Если какая-то абстрактная функция не будет перегружена в классе наследнике – компилятор выдаст сообщение об ошибке.
В результате функции базового класса могут безопасно вызывать любые свои функции, в том числе и виртуальные, потому что они гарантированно будут перегружены в классах наследниках.
Рассмотрим как будет выглядеть описание абстрактного базового класса Деталь из примера 5.1. Реализация базоваго класса, и описание и реализация классов-наследников не изменятся.
Тестирующая функция для абстрактного класса:
ООП. Часть 6. Абстрактные классы и интерфейсы
Узнайте истинную мощь наследования и полиморфизма! Раскрываем секреты абстрактных классов и интерфейсов.
В предыдущей статье мы увидели, насколько удобнее становится ООП благодаря наследованию. Но оно может стать ещё лучше, если использовать абстрактные классы и интерфейсы.
Все статьи про ООП
Пишет о программировании, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.
Абстрактные классы
Особенность абстрактных классов в том, что их можно использовать только как родительский класс, то есть вы не можете создать объект. Для их объявления используется ключевое слово abstract.
Это может понадобиться, чтобы объединить реализацию других схожих классов. Например, в вашей игре должны быть персонаж игрока и NPC (неигровые персонажи). У них могут быть общие свойства (имя, координаты) и методы (перемещение, изменение анимации).
Чтобы не повторять код несколько раз, можно вынести реализацию этих свойств и методов в абстрактный класс Character:
Тут всё как у обычных классов, но в конце можно заметить объявление свойства и метода без реализации. Реализация этих абстрактных свойств должна находиться в дочернем классе:
Когда объявляется реализация такого члена класса, необходимо указать ключевое слово override. Абстрактными могут быть следующие члены класса:
Дочерний класс должен реализовывать все члены родительского абстрактного класса, кроме тех случаев, когда дочерний класс тоже абстрактный.
В остальном всё очень похоже на обычные классы. Например, поле Y класса Character публичное, чтобы можно было использовать его в свойстве Y дочерних классов.
Абстрактный класс должен быть публичным.
BestProg
Содержание
Поиск на других ресурсах:
1. Что такое абстрактный класс? Назначение абстрактных классов. Общая форма. Ключевое слово abstract
Абстрактный класс – это класс, содержащий методы, которые не имеют реализации. Абстрактный класс создается с целью создания общего интерфейса между разными реализациями классов, которые будут производными от абстрактного класса. Абстрактный класс создается для определения некоторых общих черт производных от него классов, которые определяют конкретную его реализацию.
Запрещено (нет смысла) создавать объект абстрактного класса.
Общая форма объявления абстрактного класса следующая:
2. Что такое абстрактный метод? Общая форма
Если некоторый класс есть унаследованным от абстрактного, то этот класс должен переопределить все абстрактные методы базового абстрактного класса. В противном случае будет сгенерирована ошибка.
Общая форма объявления абстрактного метода в абстрактном классе имеет следующий вид:
В иерархии наследования (расширения) классов, абстрактные методы являются чем-то общим. Конкретные реализации абстрактных методов помещаются классах, унаследованных от абстрактных классов.
3. Схематическое изображение объявления и использования абстрактного метода в абстрактном классе. Пример
Рисунок. Схема взаимодействия между абстрактным классом и производными классами в Java
4. Пример, который демонстрирует использование абстрактных классов
В классе Figure объявляются:
В классе Triangle() реализованы:
В результате выполнения функции main() класса UseAbstractClass будет выведен следующий результат:
5. Объяснение к примеру из пункта 4
Объяснение к примеру (см. предшествующий пункт) в виде вопросов.
5.1. Зачем в классе Figure методы Area() и ShowName() объявляются абстрактными?
5.2. Почему класс Figure объявляется абстрактным?
5.3. Почему в классе Figure методы Area() и ShowName() не содержат кода реализации (тела метода)?
Если в абстрактном классе метод объявлен как абстрактный (с ключевым словом abstract ), то этот метод не должен содержать реализации (согласно синтаксису Java). Это поясняется тем, что вызов этого метода не имеет смысла.
5.4. Можно ли в абстрактном классе Figure добавлять другие не абстрактные методы?
Да, можно. Абстрактный класс может содержать не абстрактные методы (в отличие от интерфейса ).
Нет, нельзя. То есть, следующая строка
есть ошибкой компилятора Java: «Cannot instantiate the type Figure».
Метод GetArea() получает ссылку с именем f абстрактного класса Figure, который есть базовым в иерархии классов (из класса Figure унаследованы два класса Triangle и Circle).
Затем по ссылке вызывается метод Area() в строке
В функции main() при вызове метода GetArea()
Точно таким же способом связывается экземпляр f2 класса Circle с обобщенной ссылкой f в методе GetArea()
6. Можно ли в абстрактном классе объявлять методы, которые имеют реализацию (тело)?
Да, можно. Абстрактный класс допускает реализацию не абстрактных методов.
7. Пример создания иерархии абстрактных классов
Пример. Ниже приведен пример иерархии абстрактных классов
Использование класса C может быть, например следующим
8. Может ли абстрактный класс не содержать абстрактных методов?
Да, может. Это необходимо в случаях, когда абстрактные методы в классе не нужны, но нужно запретить создание экземпляров этого класса.
9. Какие отличия между использованием абстрактных классов и использованием интерфейсов?
Между абстрактными классами и интерфейсами существуют следующие отличия:
10. Преимущества использования абстрактных классов
Использование абстрактных классов дает следующие преимущества:
Разработка интерфейсных классов на С++
Интерфейсные классы весьма широко используются в программах на C++. Но, к сожалению, при реализации решений на основе интерфейсных классов часто допускаются ошибки. В статье описано, как правильно проектировать интерфейсные классы, рассмотрено несколько вариантов. Подробно описано использование интеллектуальных указателей. Приведен пример реализации класса исключения и шаблона класса коллекции на основе интерфейсных классов.
Оглавление
Введение
Интерфейсным классом называется класс, не имеющий данных и состоящий в основном из чисто виртуальных функций. Такое решение позволяет полностью отделить реализацию от интерфейса — клиент использует интерфейсный класс, — в другом месте создается производный класс, в котором переопределяются чисто виртуальные функции и определяется функция-фабрика. Детали реализации полностью скрыты от клиента. Таким образом реализуется истинная инкапсуляция, невозможная при использовании обычного класса. Про интерфейсные классы можно почитать у Скотта Мейерса [Meyers2]. Интерфейсные классы также называют классами-протоколами.
Использование интерфейсных классов позволяет ослабить зависимости между разными частями проекта, что упрощает командную разработку, снижается время компиляции/сборки. Интерфейсные классы делают более простой реализацию гибких, динамических решений, когда модули подгружаются выборочно во время исполнения. Использование интерфейсных классов в качестве интерфейса (API) библиотек (SDK) упрощает решение проблем двоичной совместимости.
Интерфейсные классы используются достаточно широко, с их помощью реализуют интерфейс (API) библиотек (SDK), интерфейс подключаемых модулей (plugin’ов) и многое другое. Многие паттерны Банды Четырех [GoF] естественным образом реализуются с помощью интерфейсных классов. К интерфейсным классам можно отнести COM-интерфейсы. Но, к сожалению, при реализации решений на основе интерфейсныx классов часто допускаются ошибки. Попробуем навести ясность в этом вопросе.
1. Специальные функции-члены, создание и удаление объектов
В этом разделе кратко описывается ряд особенностей C++, которые надо знать, чтобы полностью понимать решения, предлагаемые для интерфейсных классов.
1.1. Специальные функции-члены
Если программист не определил функции-члены класса из следующего списка — конструктор по умолчанию, копирующий конструктор, оператор копирующего присваивания, деструктор, — то компилятор может сделать это за него. С++11 добавил к этому списку перемещающий конструктор и оператор перемещающего присваивания. Эти функции-члены называются специальные функции-члены. Они генерируются, только если они используются, и выполняются дополнительные условия, специфичные для каждой функции. Обратим внимание, на то, что это использование может оказаться достаточно скрытым (например, при реализации наследования). Если требуемая функция не может быть сгенерирована, выдается ошибка. (За исключением перемещающих операций, они заменяются на копирующие.) Генерируемые компилятором функции-члены являются открытыми и встраиваемыми.
Специальные функции-члены не наследуются, если в производном классе требуется специальная функция-член, то компилятор всегда будет пытаться ее генерировать, наличие определенной программистом соответствующей функции-члена в базовом классе на это не влияет.
Подробности о специальных функциях-членах можно найти в [Meyers3].
1.2. Создание и удаление объектов — основные подробности
Создание и удаление объектов с помощью операторов new/delete — это типичная операция «два в одном». При вызове new сначала выделяется память для объекта. Если выделение прошло успешно, то вызывается конструктор. Если конструктор выбрасывает исключение, то выделенная память освобождается. При вызове оператора delete все происходит в обратном порядке: сначала вызывается деструктор, потом освобождается память. Деструктор не должен выбрасывать исключений.
Если оператор new используется для создания массива объектов, то сначала выделяется память для всего массива. Если выделение прошло успешно, то вызывается конструктор по умолчанию для каждого элемента массива начиная с нулевого. Если какой-нибудь конструктор выбрасывает исключение, то для всех созданных элементов массива вызывается деструктор в порядке, обратном вызову конструктора, затем выделенная память освобождается. Для удаления массива надо вызвать оператор delete[] (называется оператор delete для массивов), при этом для всех элементов массива вызывается деструктор в порядке, обратном вызову конструктора, затем выделенная память освобождается.
Внимание! Необходимо вызывать правильную форму оператора delete в зависимости от того, удаляется одиночный объект или массив. Это правило надо соблюдать неукоснительно, иначе можно получить неопределенное поведение, то есть может случиться все, что угодно: утечки памяти, аварийное завершение и т.д. Подробнее см. [Meyers2].
Любую форму оператора delete безопасно применять к нулевому указателю.
В приведенном выше описании необходимо сделать одно уточнение. Для так называемых тривиальных типов (встроенные типы, структуры в стиле С), конструктор может не вызываться, а деструктор в любом случае ничего не делает. См. также раздел 1.6.
1.3. Уровень доступа деструктора
1.4. Создание и удаление в одном модуле
Если оператор new создал объект, то вызов оператора delete для его удаления должен быть в том же модуле. Образно говоря, «положи туда, где взял». Это правило хорошо известно, см., например [Sutter/Alexandrescu]. При нарушении этого правила может произойти «нестыковка» функций выделения и освобождения памяти, что, как правило, приводит к аварийному завершению программы.
1.5. Полиморфное удаление
1.6. Удаление при неполном объявлении класса
warning C4150: deletion of pointer to incomplete type ‘X’; no destructor called
Ситуация эта не надумана, она легко может возникнуть при использовании классов типа интеллектуального указателя или классов-дескрипторов. Скотт Мейерс разбирается с этой проблемой в [Meyers3].
2. Чисто виртуальные функции и абстрактные классы
Концепция интерфейсных классов базируется на таких понятиях С++ как чисто виртуальные функции и абстрактные классы.
2.1. Чисто виртуальные функции
В отличии от обычной виртуальной функции, чисто виртуальную функцию можно не определять (за исключением деструктора, см. раздел 2.3), но она должна быть переопределена в одном из производных классов.
Чисто виртуальные функции могут быть определены. Герб Саттер предлагает несколько полезных применений для этой возможности [Shutter].
2.2. Абстрактные классы
Абстрактным классом называется класс, имеющий хотя бы одну чисто виртуальную функцию. Абстрактным будет также класс, производный от абстрактного класса и не переопределяющий хотя бы одну чисто виртуальную функцию. Стандарт С++ запрещает создавать экземпляры абстрактного класса, можно создавать только экземпляры производных не абстрактных классов. Таким образом, абстрактный класс создается, чтобы использоваться в качестве базового класса. Соответственно, если в абстрактном классе определяется конструктор, то его не имеет смысла делать открытым, он должен быть защищенным.
2.3. Чисто виртуальный деструктор
В ряде случаев чисто виртуальным целесообразно сделать деструктор. Но такое решение имеет две особенности.
Пример использования чисто виртуального деструктора можно найти в разделе 4.4.
3. Интерфейсные классы
Интерфейсным классом называется абстрактный класс, не имеющий данных и состоящий в основном из чисто виртуальных функций. Такой класс может иметь обычные виртуальные функции (не чисто виртуальные), например деструктор. Также могут быть статические функции-члены, например функции-фабрики.
3.1. Реализации
Реализацией интерфейсного класса будем называть производный класс, в котором переопределены чисто виртуальные функции. Реализаций одного и того же интерфейсного класса может быть несколько, причем возможны две схемы: горизонтальная, когда несколько разных классов наследуют один и тот же интерфейсный класс, и вертикальная, когда интерфейсный класс является корнем полиморфной иерархии. Конечно, могут быть и гибриды.
Ключевым моментом концепции интерфейсных классов является полное отделение интерфейса от реализации — клиент работает только с интерфейсным классом, реализация ему не доступна.
3.2. Создание объекта
Недоступность класса реализации вызывает определенные проблемы при создании объектов. Клиент должен создать экземпляр класса реализации и получить указатель на интерфейсный класс, через который и будет осуществляться доступ к объекту. Так как класс реализации не доступен, то использовать конструктор нельзя, поэтому используется функция-фабрика, определяемая на стороне реализации. Эта функция обычно создает объект с помощью оператора new и возвращает указатель на созданный объект, приведенный к указателю на интерфейсный класс. Функция-фабрика может быть статическим членом интерфейсного класса, но это не обязательно, она, например, может быть членом специального класса-фабрики (который, в свою очередь, сам может быть интерфейсным) или свободной функцией. Функция-фабрика может возвращать не сырой указатель на интерфейсный класс, а интеллектуальный. Этот вариант рассмотрен в разделах 3.3.4 и 4.3.2.
3.3. Удаление объекта
Удаление объекта является чрезвычайно ответственной операцией. При ошибке возникает либо утечка памяти, либо двойное удаление, которое обычно приводит к аварийному завершению программы. Ниже этот вопрос рассматривается максимально подробно, причем много внимания уделяется предупреждению ошибочных действий клиента.
Существуют четыре основных варианта:
3.3.1. Использование оператора delete
3.3.2. Использование специальной виртуальной функции
В этом варианте попытка удаления объекта с помощью оператора delete может компилироваться и даже выполняться, но это является ошибкой. Для ее предотвращения в интерфейсном классе достаточно иметь пустой или чисто виртуальный защищенный деструктор (см. раздел 1.3). Отметим, что использование оператора delete может оказаться достаточно сильно замаскированным, например, стандартные интеллектуальные указатели для удаления объекта по умолчанию используют оператор delete и соответствующий код глубоко «зарыт» в их реализации. Защищенный деструктор позволяет обнаружить все такие попытки на этапе компиляции.
3.3.3. Использование внешней функции
Этот вариант может привлечь определенной симметрией процедур создания и удаления объекта, но реально он никаких преимуществ по сравнению с предыдущим вариантом не имеет, а вот дополнительных проблем появляется много. Этот вариант не рекомендуется к использованию и в дальнейшем не рассматривается.
3.3.4. Автоматическое удаление с помощью интеллектуального указателя
3.4. Другие варианты управления временем жизни экземпляра класса реализации
3.5. Семантика копирования
Использование оператора копирующего присваивания не запрещено, но нельзя признать удачной идеей. Оператор копирующего присваивания всегда является парным, он должен идти в паре с копирующим конструктором. Оператор, генерируемый компилятором по умолчанию, бессмыслен, он ничего не делает. Теоретически можно объявить оператор присваивания чисто виртуальным с последующим переопределением, но виртуальное присваивание является не рекомендуемой практикой, подробности можно найти в [Meyers1]. К тому же присваивание выглядит весьма неестественно: доступ к объектам класса реализации обычно осуществляется через указатель на интерфейсный класс, поэтому присваивание будет выглядеть так:
Оператор присваивания лучше всего запретить, а при необходимости подобной семантики иметь в интерфейсном классе соответствующую виртуальную функцию.
Запретить присваивание можно двумя способами.
3.6. Конструктор интерфейсного класса
Часто конструктор интерфейсного класса не объявляется. В этом случае компилятор генерирует конструктор по умолчанию, необходимый для реализации наследования (см. раздел 1.1). Этот конструктор открытый, хотя достаточно, чтобы он был защищенным. Если в интерфейсном классе копирующий конструктор объявлен удаленным ( =delete ), то генерация компилятором конструктора по умолчанию подавляется, и необходимо явно объявить такой конструктор. Естественно его сделать защищенным с определением по умолчанию ( =default ). В принципе, объявление такого защищенного конструктора можно делать всегда. Пример находится в разделе 4.4.
3.7. Двунаправленное взаимодействие
Интерфейсные классы удобно использовать для организации двунаправленного взаимодействия. Если некоторый модуль доступен через интерфейсные классы, то клиент также может создать реализации некоторых интерфейсных классов и передать указатели на них в модуль. Через эти указатели модуль может получать сервисы от клиента а также передавать клиенту данные или нотификации.
3.8. Интеллектуальные указатели
Если интерфейсный класс поддерживает счетчик ссылок, то целесообразно использовать не стандартные интеллектуальные указатели, а специально написанный для такого случая, это сделать достаточно легко.
3.9. Константные функции-члены
Следует с осторожностью объявлять функции-члены интерфейсных классов как const. Одним из важных достоинств интерфейсных классов является возможность максимально полного отделения интерфейса от реализации, но ограничения, связанные с константностью функции-члена, могут создать проблемы при разработке класса реализации.
3.10. COM-интерфейсы
COM-интерфейсы являются примером интерфейсных классов, но следует иметь в виду, что COM — это независимый от языка программирования стандарт, и COM-интерфейсы можно реализовывать на разных языках, например на C, где нет ни деструкторов, ни защищенных членов. Разработка COM-интерфейсов на C++ должна вестись в соответствии с правилами, определяемыми технологией COM.
3.11. Интерфейсные классы и библиотеки
Достаточно часто интерфейсные классы используются в качестве интерфейса (API) для целых библиотек (SDK). В этом случае целесообразно следовать следующей схеме. Библиотека имеет доступную функцию-фабрику, которая возвращает указатель на интерфейсный класс-фабрику, с помощью которого и создаются экземпляры классов реализации других интерфейсных классов. В этом случае для библиотек, поддерживающих явную спецификацию экспорта (Windows DLL), требуется всего одна точка экспорта: вышеупомянутая функция-фабрика. Весь остальной интерфейс библиотеки становится доступным через таблицы виртуальных функций. Именно такая схема позволяет максимально просто реализовывать гибкие, динамические решения, когда модули подгружаются выборочно во время исполнения. Модуль загружается с помощью LoadLibrary() или ее аналогом на других платформах, далее получается адрес функции-фабрики, и после этого библиотека становится полностью доступной.
4. Пример интерфейсного класса и его реализации
4.1. Интерфейсный класс
Так как интерфейсный класс редко бывает один, то обычно целесообразно создать базовый класс.
Вот демонстрационный интерфейсный класс.
4.2. Класс реализации
В классе реализации деструктор является защищенным, конструктор, а также наследование от интерфейсного класса являются закрытыми, а функция-фабрика объявлена другом, это обеспечивает максимальную инкапсуляцию процедуры создания и удаления объекта.
4.3. Стандартные интеллектуальные указатели
4.3.1. Создание на стороне клиента
При создании интеллектуального указателя на стороне клиента необходимо использовать пользовательский удалитель. Класс-удалитель очень простой (он может быть вложен в IBase ):
Для std::unique_ptr<> класс-удалитель является шаблонным параметром:
Отметим, что благодаря тому, что класс-удалитель не содержит данных, размер UniquePtr равен размеру сырого указателя.
Вот шаблон функции-фабрики:
Вот шаблон преобразования из сырого указателя в интеллектуальный:
А этот ошибочный код благодаря защищенному деструктору не компилируется (конструктор должен принимать второй аргумент — объект-удалитель):
Описанная схема имеет недостаток: через интеллектуальный указатель можно вызвать виртуальную функцию удаления объекта реализации, что приведет к двойному удалению. Эту проблему можно решить так: сделать виртуальную функцию удаления защищенной, а класс-удалитель другом. Пример находится в разделе 4.4.
4.3.2. Создание на стороне реализации
Интеллектуальный указатель можно создавать на стороне реализации. В этом случае клиент получает его в качестве возвращаемого значения функциии-фабрики. Если использовать std::shared_ptr<> и в его конструктор передать указатель на класс реализации, который имеет открытый деструктор, то пользовательский удалитель не нужен (и не требуется специальная виртуальная функция для удаления объекта реализации). В этом случае конструктор std::shared_ptr<> (а это шаблон) создает объект-удалитель по умолчанию, который базируется на типе аргумента и при удалении применяет оператор delete к указателю на объект реализации. Для std::shared_ptr<> объект-удалитель входит в состав экземпляра интеллектуального указателя (точнее его управляющего блока) и тип объекта-удалителя не влияет на тип интеллектуального указателя. В этом варианте предыдущий пример можно переписать так.
Для функции-фабрики более оптимальным является вариант с использованием шаблона std::make_shared<>() :
4.4. Альтернативная реализация базового класса
Чисто виртуальный деструктор нужно определить, Delete() не чисто виртуальная функция, поэтому ее также нужно определить.
5. Исключения и коллекции, реализованные с помощью интерфейсных классов
5.1 Исключения
Если модуль, доступный через интерфейсные классы, проектируется как модуль, использующий исключения для сообщения об ошибках, то можно предложить следующий вариант реализации класса исключения.
Реализовать Exception можно, например, следующим образом.
Класс реализации IException :
Определение конструктора Exception :
5.2 Коллекции
Шаблон интерфейсного класса-коллекции может выглядеть следующим образом:
С такой коллекцией уже можно достаточно комфортно работать, но при желании указатель на такой шаблонный класс можно обернуть в шаблон класса-контейнера, который предоставляет интерфейс в стиле контейнеров стандартной библиотеки.
6. Интерфейсные классы и классы-обертки
7. Итоги
Объект реализации интерфейсного класса создается функцией-фабрикой, которая возвращает указатель или интеллектуальный указатель на интерфейсный класс.
Для удаления объекта реализации интерфейсного класса существуют три варианта.
В первом варианте интерфейсный класс должен иметь открытый виртуальный деструктор.
Семантика копирования для объектов реализации интерфейсного класса реализуется с помощью специальных виртуальных функций.
Интерфейсные классы позволяют упростить компоновку модулей, почти весь интерфейс модуля становится доступным через таблицы виртуальных функций, поэтому не сложно реализовывать гибкие, динамические решения, когда модули подгружаются выборочно во время исполнения.
Список литературы
[GoF]
Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования.: Пер. с англ. — СПб.: Питер, 2001.
[Josuttis]
Джосаттис, Николаи М. Стандартная библиотека C++: справочное руководство, 2-е изд.: Пер. с англ. — М.: ООО «И.Д. Вильямс», 2014.
[Dewhurst]
Дьюхэрст, Стефан К. Скользкие места C++. Как избежать проблем при проектировании и компиляции ваших программ.: Пер. с англ. — М.: ДМК Пресс, 2012.
[Meyers1]
Мейерс, Скотт. Наиболее эффективное использование C++. 35 новых рекомендаций по улучшению ваших программ и проектов.: Пер. с англ. — М.: ДМК Пресс, 2000.
[Meyers2]
Мейерс, Скотт. Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ.: Пер. с англ. — М.: ДМК Пресс, 2014.
[Meyers3]
Мейерс, Скотт. Эффективный и современный C++: 42 рекомендации по использованию C++11 и C++14.: Пер. с англ. — М.: ООО «И.Д. Вильямс», 2016.
[Sutter]
Саттер, Герб. Решение сложных задач на C++.: Пер. с англ. — М: ООО «И.Д. Вильямс», 2015.
[Sutter/Alexandrescu]
Саттер, Герб. Александреску, Андрей. Стандарты программирования на С++.: Пер. с англ. — М.: ООО «И.Д. Вильямс», 2015.