Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ | страница 37
• Компилятор может неявно генерировать для класса конструктор по умолчанию, конструктор копирования, оператор присваивания и деструктор.
Правило 6: Явно запрещайте компилятору генерировать функции, которые вам не нужны
Агенты по продаже недвижимости и программные системы, обслуживающие их деятельность, могут нуждаться в классе, представляющем дома, выставленные на продажу:
>class HomeForSale {...};
Любой агент по продаже недвижимости скажет вам, что каждый объект уникален – не бывает двух, в точности одинаковых. Вот почему идея создания копии объекта HomeForSale бессмысленна. Как можно скопировать нечто, по определению, уникальное? Поэтому хотелось бы, чтобы попытки скопировать объекты HomeForSale не компилировались:
>HomeForSale h1;
>HomeForSale h2;
>HomeForSale h3(h1); // попытка скопировать h1 –
>// не должно компилироваться!
>h1 = h2; // попытка скопировать h2 –
>// не должно компилироваться!
Увы, предотвратить такую компиляцию не так-то просто. Обычно, если вы не хотите, чтобы класс поддерживал определенного рода функциональность, вы просто не объявляете функций, которые ее реализуют. Но с конструктором копирования и оператором присваивания эта стратегия не работает, поскольку, как следует из правила 5, если вы их не объявляете, а где-то в программе производится попытка их вызвать, то компилятор сгенерирует их автоматически.
Похоже на безвыходное положение. Если вы сами не объявите конструктор копирования или оператор присваивания, то их сгенерирует компилятор. И ваш класс будет поддерживать копирование. Но то же самое произойдет, если вы объявите эти функции самостоятельно. Однако наша цель – предотвратить копирование!
Ключ к решению в том, что все сгенерированные компилятором функции являются открытыми. Чтобы предотвратить автоматическое генерирование, вы должны объявить их самостоятельно, но никто не требует, чтобы они были открытыми. Ну так и объявите конструктор копирования и оператор присваивания закрытыми. Объявляя явно функцию-член, вы предотвращаете генерирование ее компилятором, а сделав ее закрытой, не позволяете кому-либо вызывать ее.
Схема не идеальна, потому что другие члены класса и функции-друзья по-прежнему могут вызывать закрытые функции. Если только вы не включите лишь объявление, опустив определение. Тогда если кто-то случайно вызовет такую функцию, то получит сообщение об ошибке на этапе компоновки. Этот трюк – объявление функций-членов закрытыми и сознательный отказ от их реализации – как раз и используется для предотвращения копирования в некоторых классах библиотеки iostreams. Взгляните, например, на объявления классов ios_base, basic_ios и sentry в вашей реализации стандартной библиотеки. Вы обнаружите, что в каждом случае как конструктор копирования, так и оператор присваивания объявлены закрытыми и нигде не определены.