Если мы не определим эти операции, компилятор создаст их сам. Обычно создаваемые компилятором версии выполняются, копируя, присваивая или удаляя каждую переменную-член объекта. Например, когда в приложении книжного магазина (см. раздел 7.1.1) компилятор выполняет следующее присвоение:
total = trans; //
оно выполняется, как будто было написано так:
//
total.bookNo = trans.bookNo;
total.units_sold = trans.units_sold;
total.revenue = trans.revenue;
Более подробная информация об определении собственных версий этих операторов приведена в главе 13.
Хотя компилятор и создает сам операторы копирования, присвоения и удаления, важно понимать, что у некоторых классов их стандартные версии ведут себя неправильно. В частности, синтезируемые версии вряд ли будут правильно работать с классами, которые резервируют ресурсы, располагающиеся вне самих объектов класса. Пример резервирования и управления динамической памятью приведен в главе 12. Как будет продемонстрировано в разделе 13.6, классы, которые управляют динамической памятью, вообще не могут полагаться на синтезируемые версии этих операций.
Однако следует заметить, что большинство классов, нуждающихся в динамической памяти, способны (и должны) использовать классы vector
или string
, если им нужно управляемое хранение. Классы, использующие векторы и строки, избегают сложностей, связанных с резервированием и освобождением памяти.
Кроме того, синтезируемые версии операторов копирования, присвоения и удаления правильно работают для классов, у которых есть переменные-члены класса vector
или string
. При копировании или присвоении объекта, обладающего переменной-членом класса vector
, этот класс сам позаботится о копировании и присвоении своих элементов. Когда объект удаляется, переменная-член класса vector
тоже удаляется, что в свою очередь удаляет элементы вектора. Класс string
работает аналогично.
На настоящий момент для нашего класса определен интерфейс; однако ничто не вынуждает пользователей использовать его. Наш класс еще не использует инкапсуляцию — пользователи вполне могут обратиться к объекту Sales_data
и воспользоваться его реализацией. Для обеспечения инкапсуляции в языке С++ используют
• Члены класса, определенные после спецификатора public
, доступны для всех частей программы.
• Члены, определенные после спецификатора private
, являются private
инкапсулируют (т.е. скрывают) реализацию.
Переопределив класс Sales_data
еще раз, получаем следующее:
class Sales_data {
public: //
Sales_data() = default;
Sales_data(const std::string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) { }
Sales_data(const std::string &s): bookNo(s) { }
Sales_data(std::istream&);
std::string isbn() const { return bookNo; }
Sales_data &combine(const Sales_data&);
private: //
double avg_price() const
{ return units_sold ? revenue/units_sold : 0; }
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
Конструкторы и функции-члены, являющиеся частью интерфейса (например, isbn()
и combine()
), должны располагаться за спецификатором public
; переменные-члены и функции, являющиеся частью реализации, располагаются за спецификатором private
.
Класс может содержать любое количество спецификаторов доступа; нет никаких ограничений на то, как часто используется спецификатор. Каждый спецификатор определяет уровень доступа последующих членов. Заданный уровень доступа остается в силе до следующего спецификатора доступа или до конца тела класса.
class
и struct