Правила программирования на Си и Си++

       

Избегайте перегрузки функций и аргументов, используемых по умолчанию


Это правило не применяется к конструкторам и функциям перегрузки операций.

Перегрузка функций, подобно многим другим свойствам Си++, была добавлена к этому языку по особым причинам. Не позволяйте себя увлечь этим. Функции, которые делают разные вещи, должны иметь и разные имена.

Перегруженные функции обычно вызывают больше проблем, чем их решают. Во-первых, проблема двусмысленности:

f( int, long

);

f( long, int

);

f( 10, 10 );         // ОШИБКА: Какую из функций я вызываю?

Более коварно следующее:

f( int );

f( void* );

f( 0 );              // ОШИБКА: Вызов двусмысленный

Проблемой здесь является Си++, который считает, что 0



может быть как указателем, так и типом int. Если вы делаете так:

const void

*NULL = 0;

const int   ZERO = 0;

то вы можете записать f(NULL) для выбора варианта с указателем и f(ZERO) для доступа к целочисленному варианту, но это ведет к большой путанице. В такой ситуации вам бы лучше просто использовать функции с двумя разными именами.

Аргументы по умолчанию, создающие на самом деле перегруженные функции (по одной на каждую возможную комбинацию аргументов), также вызывают проблемы. Например, если вы написали:

f( int x = 0 );

и затем случайно вызвали f() без аргументов, компилятор успешно и без возражений вставит 0. Все, чего вы добились, — это устранили то, что в ином случае вызвало бы полезное сообщение об ошибке во время компиляции, и сдвинули ошибку на этап выполнения.

Исключениями из сказанного выше являются перегруженные операции и конструкторы; многие классы имеют их по нескольку, и аргументы по умолчанию часто имеют смысл в конструкторах. Код, подобный следующему, вполне приемлем:

class string

{

public:

   string( char *s = ""     );

   string( const string r  );

   string( const CString r ); // преобразование из класса MFC.

   // ...

};

Для пояснения: разные классы будут часто обрабатывать одно и то же сообщение, реализуя функции-обработчики с совпадающими именами. Например, большинство классов реализуют сообщение print(). Смысл того, что я пытаюсь здесь добиться, такой: плохая мысль - в одном классе иметь много обработчиков сообщений с одним и тем же именем. Вместо:


class string
{
 // ...
public:
   print( FILE     *fp  );
   print( iostream ios );
   print( window   win );
я бы рекомендовал:
class string
{
// ...
public:
   print_file   ( FILE     *fp  );
   print_stream ( iostream ios );
   print_window ( window   win );
Еще лучше, если бы у вас был класс устройства device, который бы мог представлять типы: файловый FILE, потоковый iostream и оконный window, в зависимости от того, как он инициализируется — тогда бы вы могли реализовать единственную функцию print(), принимающую в качестве аргумента device.
Я должен сказать, что сам порой нарушаю это правило, но делаю это, зная, что, переступив черту, могу навлечь на себя беду.
Часть 8б. Проблемы сцепления
Концепция сцепления описана ранее в общем виде. Я также указал наиболее важное правило Си++ для сокращения числа отношений сцепления: "Все данные должны быть закрытыми". Идея минимизации связей на самом деле центральная для Си++. Вы можете возразить, что главной целью объектно-ориентированного проектирования является минимизация отношений связи посредством инкапсуляции. Этот раздел содержит специфические для Си++ правила, касающиеся связывания.

Содержание раздела