Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация
- Название:Стандарты программирования на С++. 101 правило и рекомендация
- Автор:
- Жанр:
- Издательство:Издательский дом Вильямс
- Год:2005
- Город:Москва
- ISBN:5-8459-0859-0
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Герб Саттер - Стандарты программирования на С++. 101 правило и рекомендация краткое содержание
Эта книга поможет новичку стать профессионалом, так как в ней представлен сконцентрированный лучший опыт программистов на С++, обобщенный двумя экспертами мирового класса.
Начинающий программист найдет в ней простые и понятные рекомендации для ежедневного использования, подкрепленные примерами их конкретного применения на практике.
Опытные программисты найдут в ней советы и новые рекомендации, которые можно сразу же принять на вооружение. Программисты-профессионалы могут использовать эту книгу как основу для разработки собственных стандартов кодирования, как для себя лично, так и для группы, которой они руководят.
Конечно, книга рассчитана в первую очередь на профессиональных программистов с глубокими знаниями языка, однако она будет полезна любому, кто захочет углубить свои знания в данной области.
Стандарты программирования на С++. 101 правило и рекомендация - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
// Объявляем копирующий конструктор как explicit (у данного
// решения имеется побочное действие, так что требуется
// улучшение этого метода)
class B { // ...
public:
explicit B(const B& rhs);
};
class D : public B { /* ... */ };
Вызывающий код все равно в состоянии выполнить срезку, если это необходимо, но должен делать это явно:
void Transmogrify(B obj); // Теперь эта функция вообще не
// может быть вызвана (!)
void Transmogrify2(const B& obj) // Идиома для намерения в
{ // любом случае получить
В b( obj ); // параметр obj по значению
// ... // (с возможной срезкой)
}
B b; // Базовые классы не должны быть конкретными
D d; // (см. рекомендацию 35), но допустим это
Transmogrify(b); // Должна быть ошибка (см. примечание)
Transmogrify(d); // Должна быть ошибка (см. примечание)
Transmogrify2(d); // Все в порядке
Примечание: на момент написания данной рекомендации некоторые компиляторы ошибочно допускали один или оба приведенных вызова функции Transmogrify
. Эта идиома вполне стандартна, но (пока что) не полностью переносима.
Имеется лучший способ предупреждения срезки, с более высокой степенью переносимости. Пусть, например, функция наподобие Transmogrify
в действительности хочет получить полную глубокую копию без информации о действительном производном типе переданного объекта. Более общее идиоматическое решение состоит в том, чтобы сделать копирующий конструктор базового класса защищенным (чтобы функция наподобие Transmogrify
не могла случайно его вызвать), а вместо него воспользоваться виртуальной функцией Clone
:
// добавление функции Clone (уже лучше, но все еще требуется
// усовершенствование)
class B { // ...
public:
virtual B* Clone() const = 0;
protected:
B(const B&);
};
class D : public B { // ...
public:
virtual D* Clone() const { return new D(*this); }
protected:
D( const D& rhs ): B(rhs) {/*...*/ }
};
Теперь попытка срезки будет (переносимо) генерировать ошибку времени компиляции, а объявление функции Clone
как чисто виртуальной заставляет непосредственный производный класс перекрыть ее. К сожалению, с данным решением все еще связаны две проблемы, которые компилятор не в состоянии обнаружить: в классе, производном от производного, функция Clone
может оказаться неперекрытой, а перекрытие Clone
может реализовать ее некорректно, так что копия будет не того же типа, что и оригинал. Функция Clone
должна следовать шаблону проектирования Nonvirtual Interface (NVI; см. рекомендацию 39), который разделяет открытую и виртуальную природы Clone
и позволяет вам использовать ряд важных проверок:
class В { // ...
publiс:
B* Clone() const { // Невиртуальная функция
B* р = DoClone();
assert(typeid(*p) == typeid(*this) &&
"DoClone incorrectly overridden");
return p; // проверка типа, возвращаемого DoClone
}
protected:
B(const B&);
private:
virtual B* DoClone() const = 0;
};
Функция Clone
теперь является невиртуальным интерфейсом, используемым вызывающим кодом. Производные классы должны перекрыть функцию DoClone
. Дополнительная проверка обнаружит все копии, которые имеют тип, отличный от оригинала, тем самым оповещая, что в некотором производном классе не перекрыта функция DoClone
; в конце концов, задача assert
состоит именно в обнаружении и сообщении о таких программных ошибках (см. рекомендации 68 и 70).
Некоторые проектные решения могут требовать, чтобы копирующие конструкторы базовых классов оставались открытыми (например, когда часть вашей иерархии представляет собой библиотеку стороннего производителя). В таком случае следует предпочесть передачу посредством (интеллектуального) указателя передаче по ссылке; как показано в рекомендации 25, передача посредством указателя существенно менее подвержена срезке и нежелательному созданию временных объектов.
[Dewhurst03] §30, §76, §94 • [Meyers96] §13 • [Meyers97] §22 • [Stroustrup94] §11.4.4 • [Stroustrup00] §12.2.3
55. Предпочитайте канонический вид присваивания
При реализации оператора operator=
предпочитайте использовать канонический вид — невиртуальный с определенной сигнатурой.
Предпочтительно объявлять копирующее присваивание для типа T
с одной из следующих сигнатур (см. [Stroustrup00] и [Alexandrescu03a]):
T& operator=(const T&); // классический вид
T& operator=(T); // потенциально оптимизированный
// вид (см. рекомендацию 27)
Второй вариант имеет смысл использовать, если вам в любом случае требуется копия аргумента в теле вашего оператора, как, например, при использовании идиомы, основанной на использовании функции обмена (см. рекомендацию 56).
Избегайте делать любой оператор присваивания виртуальным (см. [Meyers96] §33 и [Sutter04] §19). Если вы полагаете, что вам требуется виртуальное поведение присваивания, обратитесь сначала к указанной литературе. Если и после этого вы стоите на своем, то лучше использовать виртуальную именованную функцию, а не оператор (например, virtual void Assign(const T&);
).
He возвращайте const T&
. Хотя этот тип возвращаемого значения имеет то преимущество, что защищает от странных присваиваний наподобие (a=b)=c
, главным его недостатком является то, что вы не сможете поместить объекты типа T
в контейнеры стандартной библиотеки; эти контейнеры требуют, чтобы оператор присваивания возвращал тип T&
.
Всегда делайте копирующее присваивание безопасным в смысле исключений, причем предпочтительна строгая гарантия (см. рекомендацию 71).
Убедитесь, что ваш оператор присваивания безопасен в смысле присваивания самому себе. Избегайте написания оператора копирующего присваивания, который для корректной работы полагается на проверку присваивания самому себе; зачастую это говорит о недостаточной безопасности в смысле исключений. Если вы пишете копирующее присваивание с использованием идиомы обмена (см. рекомендацию 56), то вы автоматически обеспечиваете как строгую безопасность в смысле исключений, так и безопасность в смысле присваивания самому себе. Если присваивание самому себе часто встречается в программе из-за использования ссылочных синонимов или по каким-то иным причинам, проверка присваивания самому себе может использоваться в качестве средства оптимизации во избежание лишней работы.
Явно вызывайте все операторы присваивания базовых классов и всех данных-членов ([Meyers97] §16); обратите внимание, что идиома обмена автоматически заботится обо всех этих вещах. Возвращайте из оператора присваивания значение *this
([Meyers97] §15).
Интервал:
Закладка: