необходимости перекрывать их все (точнее говоря, данный пример можно рассматривать как применение шаблона проектирования Template Method).
•
•
См. также рекомендацию 54.
NVI не применим к деструкторам в связи со специальным порядком их выполнения (см. рекомендацию 50).
NVI непосредственно не поддерживает ковариантные возвращаемые типы. Если вам требуется ковариантность, видимая вызывающему коду без использования dynamic_cast (см. также рекомендацию 93), проще сделать виртуальную функцию открытой.
40. Избегайте возможностей неявного преобразования типов
Не все изменения прогрессивны: неявные преобразования зачастую приносят больше вреда, чем пользы. Дважды подумайте перед тем, как предоставить возможность неявного преобразования к типу и из типа, который вы определяете, и предпочитайте полагаться на явные преобразования (используйте конструкторы, объявленные как explicit
, и именованные функции преобразования типов).
Неявные преобразования типов имеют две основные проблемы.
• Они могут проявиться в самых неожиданных местах.
• Они не всегда хорошо согласуются с остальными частями языка программирования.
Неявно преобразующие конструкторы (конструкторы, которые могут быть вызваны с одним аргументом и не объявлены как explicit
) плохо взаимодействуют с перегрузкой и приводят к созданию невидимых временных объектов. Преобразования типов, определенные как функции-члены вида operator T
(где T
— тип), ничуть не лучше — они плохо взаимодействуют с неявными конструкторами и позволяют без ошибок скомпилировать разнообразные бессмысленные фрагменты кода (примеров чего несть числа — см. приведенные в конце рекомендации ссылки; мы приведем здесь только пару из них).
В С++ последовательность преобразований типов может включать не более одного пользовательского преобразования. Однако когда в эту последовательность добавляются встроенные преобразования, ситуация может оказаться предельно запутанной. Решение здесь простое и состоит в следующем.
• По умолчанию используйте explicit в конструкторах с одним аргументом (см. рекомендацию 54):
class Widget { // ...
explicit Widget(unsigned int widgetizationFactor);
explicit Widget(const char* name, const Widget* other = 0);
};
• Используйте для преобразований типов именованные функции, а не соответствующие операторы:
class String { // ...
const char* as_char_pointer() const; // в традициях c_str
};
См. также обсуждение копирующих конструкторов, объявленных как explicit
, в рекомендации 54.
Widget::Widget (unsigned int)
, который может быть вызван неявно, и функция Display
, перегруженная для Widget
и double
. Рассмотрим следующий сюрприз при разрешении перегрузки:
void Display(double); // вывод double
void Display(const Widget&); // Вывод Widget
Display(5); // гм! Создание и вывод Widget
String
оператором operator const char*
:
class String {
// ...
public:
operator const char*(); // Грустное решение...
};
В результате этого становятся компилируемыми масса глупостей и опечаток. Пусть s1
и s2
— объекты типа String
. Все приведенные ниже строки компилируются:
int x = s1 - s2; // Неопределенное поведение
const char* р = s1 - 5; // Неопределенное поведение
р = s1 + '0'; // делает не то, что вы ожидаете
if (s1 == '0') { ... } // делает не то, что вы ожидаете
Именно по этой причине в стандартном классе string
отсутствует operator const char*
.
При нечастом и осторожном использовании неявные преобразования типов могут сделать код более коротким и интуитивно более понятным. Стандартный класс std::string
определяет неявный конструктор, который получает один аргумент типа const char*
. Такое решение отлично работает, поскольку проектировщики класса приняли определенные меры предосторожности.
• Не имеется автоматического преобразования std::string
в const char*
; такое преобразование типов выполняются при помощи двух именованных функций —