Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
- Название:Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ
- Автор:
- Жанр:
- Издательство:Литагент «ДМК»233a80b4-1212-102e-b479-a360f6b39df7
- Год:2006
- Город:Москва
- ISBN:5-94074-304-8
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Скотт Майерс - Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ краткое содержание
Эта книга представляет собой перевод третьего издания американского бестселлера Effective C++ и является руководством по грамотному использованию языка C++. Она поможет сделать ваши программы более понятными, простыми в сопровождении и эффективными. Помимо материала, описывающего общую стратегию проектирования, книга включает в себя главы по программированию с применением шаблонов и по управлению ресурсами, а также множество советов, которые позволят усовершенствовать ваши программы и сделать работу более интересной и творческой. Книга также включает новый материал по принципам обработки исключений, паттернам проектирования и библиотечным средствам.
Издание ориентировано на программистов, знакомых с основами C++ и имеющих навыки его практического применения.
Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
}; // наличие реализации по умолчанию
// может оказаться неудачной идеей
class SpecialWindow: public Window {
public:
virtual void blink() {...}
...
};
typedef std::vector>VPW;
VPW winPtrs; // контейнер содержит
// (указатели на) все возможные
... // типы окон
for(VPW::iterator iter = winPtrs.begin();
iter != winPtrs.end();
++iter) // dynamic_cast не используется
(*iter)->blink();
Ни один из этих подходов – с применением безопасных по отношению к типам контейнеров или перемещением виртуальной функции вверх по иерархии – не является универсально применимым, но во многих случаях они представляют полезную альтернативу dynamic_cast. Пользуйтесь ими, когда возможно.
Но вот чего стоит избегать всегда – это каскадов из операторов dynamic_cast, то есть чего-то вроде такого кода:
class Window {...};
... // здесь определены производные классы
typedef std::vector> VPW;
VPW winPtrs;
...
for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
{
if (SpecialWindow1 *psw1=
dynamic_cast(iter->get())) {...}
else if (SpecialWindow2 *psw2=
dynamic_cast(iter->get())) {...}
else if (SpecialWindow2 *psw2=
dynamic_cast(iter->get())) {...}
...
}
В этом случае генерируется объемный и медленный код, к тому же он нестабилен, потому что при каждом изменении иерархии классов Window весь этот код нужно пересмотреть на предмет обновления. Например, если добавится новый производный класс, то вероятно, придется добавить еще одну ветвь в предложение if. Подобный код почти всегда должен быть заменен чем-то на основе вызова виртуальных функций.
В хорошей программе на C++ приведения типов используются очень редко, но полностью отказываться от них тоже не стоит. Так, показанное выше приведение int к double является разумным, хотя и не абсолютно необходимым (код может быть переписан с объявлением новой переменной типа double, инициируемой значением x). Как и большинство сомнительных конструкций, приведения типов должны быть изолированы насколько возможно. Обычно они помещаются внутрь функций, чей интерфейс скрывает от пользователей те некрасивые дела, что творятся внутри.
• Избегайте насколько возможно приведений типов, особенно dynamic_cast, в критичном по производительности коде. Если дизайн требует приведения, попытайтесь разработать альтернативу, где такой необходимости не возникает.
• Когда приведение типа необходимо, постарайтесь скрыть его внутри функции. Тогда пользователи смогут вызывать эту функцию вместо помещения приведения в их собственный код.
• Предпочитайте приведения в стиле C++ старому стилю. Их легче увидеть, и они более избирательны.
Правило 28: Избегайте возвращения «дескрипторов» внутренних данных
Представим, что вы работаете над приложением, имеющим дело с прямоугольниками. Каждый прямоугольник может быть представлен своим левым верхним углом и правым нижним. Чтобы объект Rectangle оставался компактным, вы можете решить, что описание определяющих его точек следует вынести из Rectangle во вспомогательную структуру:
class Point { // класс, представляющий точки
public:
Point(int x, int y);
...
void setX(int newVal);
void setY(int newVal);
...
};
struct RectData { // точки, определяющие Rectangle
Point ulhc; // ulhc – верхний левый угол
Point lrhc; // lrhc – нижний правый угол
};
class Rectangle {
...
private:
std::tr1::shared_ptr pData; // см. в правиле 13
}; // информацию о tr1::shared_ptr
Поскольку пользователям класса Rectangle понадобится определять его координаты, то класс предоставляет функции upperLeft и lowerRight. Однако Point – это определенный пользователем тип, поэтому, помня о том, что передача таких типов по ссылке обычно более эффективна, чем передача по значению (см. правило 20), эти функции возвращают ссылки на внутренние объекты Point:
class Rectangle {
public:
...
Point& upperLeft() const { return pData->ulhc;}
Point& lowerRight() const { return pData->lrhc;}
...
};
Такой вариант откомпилируется, но он неправильный! Фактически он внутренне противоречив. С одной стороны, upperLeft и lowerRight объявлены как константные функции-члены, поскольку они предназначены только для того, чтобы предоставить клиенту способ получить информацию о точках Rectangle, не давая ему возможности модифицировать объект Rectangle (см. правило 3). С другой стороны, обе функции возвращают ссылки на закрытые внутренние данные – ссылки, которые пользователь может затем использовать для модификации этих внутренних данных! Например:
Point coord1(0, 0);
Point coord2(100,100);
const Rectangle rec(coord1, coord2); // rec – константный прямоугольник
// от (0, 0) до (100, 100)
rec.upperLeft().setX(50); // теперь rec лежит между
// (50, 0) и (100, 100)!
Обратите внимание, что пользователь функции upperLeft может использовать возвращенную ссылку на один из данных-членов внутреннего объекта Point для модификации этого члена. Но ведь ожидается, что rec – константа!
Из этого примера следует извлечь два урока. Первый – член данных инкапсулирован лишь настолько, насколько доступна функция, возвращающая ссылку на него. В данном случае хотя ulhc и lrhc объявлены закрытыми, но на самом деле они открыты, потому что на них возвращают ссылки открытые функции upperLeft и lowerRight. Второй урок в том, что если константная функция-член возвращает ссылку на данные, ассоциированные с объектом, но хранящиеся вне самого объекта, то код, вызывающий эту функцию, может модифицировать данные. (Все это последствия ограничений побитовой константности – см. правило 3.)
Такой результат получился, когда мы использовали функции-члены, возвращающие ссылки, но если они возвращают указатели или итераторы, проблема остается, и причины те же. Ссылки, указатели и итераторы – все это «дескрипторы» (handles), и возвращение такого «дескриптора» внутренних данных объекта – прямой путь к нарушению принципов инкапсуляции. Как мы только что видели, это может привести к тому, что константные функции-члены позволят модифицировать состояние объекта.
Обычно, говоря о внутреннем устройстве объекта, мы имеем в виду его данные-члены, но функции-члены, к которым нет открытого доступа (то есть объявленные в секции private или protected), также являются частью внутреннего устройства. Поэтому возвращать их «дескрипторы» тоже не следует. Иными словами, нельзя, чтобы функция-член возвращала указатель на менее доступную функцию-член. В противном случае реальный уровень доступа будет определять более доступная функция, потому что клиенты смогут получить указатель на менее доступную функцию и вызвать ее через такой указатель.
Читать дальшеИнтервал:
Закладка: