// недоступна (могла бы использоваться
// функция 2, но она не рассматривается, так
// как у функции 3 лучшее соответствие
// аргументу)
В строке А обходной путь состоит в том, чтобы явно квалифицировать вызов как ::Twice(21)
для того, чтобы заставить поиск имен выбрать глобальную функцию. В строке Б обходной путь состоит в добавлении явного преобразования типа с.Twiсе(string("Hellо"))
для того, чтобы заставить разрешение перегрузки выбрать соответствующую функцию. Некоторые из таких проблем, связанных с вызовами, можно решить и без применения идиомы Pimpl, например, никогда не используя закрытые перегрузки функций-членов, но не для всех проблем, разрешимых при помощи идиомы Pimpl, можно найти такие обходные пути.
Третье следствие влияет на обработку ошибок и безопасность. Рассмотрим пример Widget
Тома Каргилла (Tom Cargill):
class Widget { // ...
public:
Widget& operator=(const Widget&);
private:
T1 t1_;
T2 t2_;
};
Коротко говоря, мы не можем написать оператор operator=
, который обеспечивает строгую гарантию (или хотя бы базовую гарантию), если операции T1
или T2
могут давать необратимые сбои (см. рекомендацию 71). Хорошие новости, однако, состоят в том, что приведенная далее простая трансформация всегда обеспечивает, как минимум, базовую гарантию для безопасного присваивания, и как правило — строгую гарантию, если необходимые операции T1
и T2
(а именно — конструкторы и деструкторы) не имеют побочных эффектов. Для этого следует хранить объекты не по значению, а посредством указателей, предпочтительно спрятанными за единственным указателем на реализацию:
class Widget { // ...
public:
Widget& operator=(const Widget&);
private:
struct Impl;
shared_ptr
};
Widget& Widget::operator=( const Widget& ) {
shared_ptr
// изменяем temp->t1_ и temp->t2_; если какая-то из
// операций дает сбой, генерируем исключение, в
// противном случае - принимаем внесенные изменения:
pimpl_ = temp;
return *this;
}
В то время как вы получаете все преимущества дополнительного уровня косвенности, проблема состоит только в увеличении сложности кода (см. рекомендации 6 и 8).
44. Предпочитайте функции, которые не являются ни членами, ни друзьями
Там, где это возможно, предпочтительно делать функции не членами и не друзьями классов.
Функции, не являющиеся членами или друзьями классов, повышают степень инкапсуляции путем снижения зависимостей: тело такой функции не может зависеть от закрытых и защищенных членов класса (см. рекомендацию 11). Они также разрушают монолитность классов, снижая связность (см. рекомендацию 33), и повышают степень обобщенности, так как сложно писать шаблоны, которые не знают, является ли интересующая нас операция членом данного типа или нет (см. рекомендацию 67).
Для определения того, должна ли функция быть реализована как член и/или друг класса, можно воспользоваться следующим алгоритмом:
Если функция представляет собой один из операторов =, ->,
[] или (), которые должны быть членами,
то
делайте данную функцию членом класса.
иначе если 1. функция требует левый аргумент иного типа
(как, например, в случае операторов >> или <<)
или 2. требует преобразования типов для левого аргумента,
или 3. может быть реализована с использованием только