Кто имеет право трогать ваши данные
Когда вы писали свой первый класс, вы, скорее всего, открыли все члены внешнему миру. Это работает, но выбрасывает одну из главных причин существования классов: инкапсуляцию — сокрытие внутреннего состояния класса, чтобы остальная часть программы могла взаимодействовать с ним только через контролируемую поверхность. Спецификаторы доступа — это то, как вы проводите эту границу.
Их ровно три: public, private и protected. Каждый помечает следующие за ним члены, и метка решает, какому коду разрешено их читать или записывать. Сделаете правильно — ваш класс сам обеспечивает свои правила; сделаете неправильно — любая ошибка где угодно может повредить состояние вашего объекта.
Три спецификатора
Спецификатор — это ключевое слово, за которым следует двоеточие. Каждый объявленный после него член — вплоть до следующего спецификатора — попадает под этот уровень доступа.
Раскомментируйте последнюю строку — и компилятор откажется собирать: balance является private, поэтому main не может трогать его напрямую. В этом и смысл: единственный способ изменить баланс — через deposit, а значит, вы сможете позже добавить проверку (никаких отрицательных вкладов, логирование, лимиты) в одном месте и быть уверены, что она всегда применяется.
Вот полная разбивка:
// доступно из...
// public откуда угодно (любой код, у которого есть объект)
// private только собственные члены класса (+ friends)
// protected собственные члены класса И производные классы (+ friends)
class против struct: значение по умолчанию
Вы можете писать сколько угодно блоков спецификаторов в любом порядке. То, что члены получают до того, как вы напишете первый, зависит от того, использовали ли вы class или struct:
- В
classчлены по умолчаниюprivate. - В
structчлены по умолчаниюpublic.
Это значение по умолчанию — единственное отличие на уровне языка между двумя ключевыми словами. У struct могут быть методы, конструкторы и private-секции точно так же, как у class.
По соглашению struct используют для простых наборов публичных данных, а class — когда нужны поведение и скрытое состояние, но компилятор это не навязывает, отличается только значение по умолчанию.
Инкапсуляция с геттерами и сеттерами
Повседневный приём таков: данные помещают в private-секцию, а public-метод даёт контролируемый доступ. Геттер только для чтения возвращает значение; сеттер проверяет перед присваиванием. Именно здесь private окупается.
Поскольку celsius является private, протащить некорректное значение невозможно — каждая запись должна пройти через setCelsius, который охраняет инвариант. Обратите внимание, что геттеры помечены const: они обещают не изменять объект, поэтому их можно вызывать и на объектах const Temperature.
protected и наследование
protected имеет значение только тогда, когда в дело вступает наследование. Для внешнего кода он ведёт себя как private, но производный класс может до него добраться. Используйте его для членов, которые законно нужны подклассу, но к которым публике всё равно не следует обращаться.
Частая ошибка новичков — хвататься за protected на каждом члене данных «на случай, если подклассу понадобится». Это тихо расширяет контракт вашего класса — теперь каждый подкласс может зависеть от этого поля, и вы не можете менять его свободно. Предпочитайте private и повышайте до protected только тогда, когда производному классу действительно нужен доступ.
Лазейка friend
Иногда одной внешней функции или классу законно нужно видеть ваши внутренности — классический случай — оператор вроде <<, который нельзя сделать членом. Ключевое слово friend предоставляет этой одной названной сущности доступ к вашим членам private и protected, и ничему больше.
friend — это намеренное, точечное исключение: сам класс называет, кому именно он доверяет, поэтому никто не может предоставить себе доступ извне. Используйте его умеренно; если вы ловите себя на добавлении множества friends, ваши члены, вероятно, изначально не должны были быть private, либо ваш дизайн нуждается в переосмыслении.
Распространённые ошибки, которых стоит избегать
- Делать все члены
public. Кажется простым, но вы теряете всю валидацию и инварианты, которые даёт инкапсуляция. По умолчанию — данныеprivateсpublic-методами. - Забывать про значение по умолчанию у
class.class Foo { int x; };делаетxприватным, поэтомуfoo.x = 5не скомпилируется. Если вы имели в виду простой набор данных, используйтеstructили добавьте меткуpublic:. - Злоупотреблять
protected. Это более слабая граница, чемprivate, и она имеет значение только при наследовании. Хвататься за неё повсюду — значит привязывать подклассы к полям, которые вы, возможно, захотите изменить. - Ожидать, что
private— это средство безопасности. Это правило времени компиляции, предотвращающее случайный доступ, а не шифрование. Байты по-прежнему лежат в памяти;private— про чистый дизайн, а не про секретность.
Далее: Структуры
Теперь вы увидели, что struct — это на самом деле просто class, члены которого по умолчанию public. Следующая страница, структуры, подробно разбирает, когда это значение public-по-умолчанию — именно то, что вам нужно (лёгкие агрегаты для группировки связанных значений), и как struct используется в идиоматичном C++ наряду с полноценными классами.
Часто задаваемые вопросы
В чём разница между public, private и protected в C++?
Члены public доступны откуда угодно. Члены private доступны только изнутри того же класса (и его friend-ов). protected похож на private, но дополнительно позволяет производным классам обращаться к члену. По соглашению данные держат private, а поведение предоставляют через public-методы.
Являются ли члены приватными по умолчанию в C++?
В class — да: всё private, пока вы не напишете спецификатор доступа. В struct по умолчанию public. Это единственное значение по умолчанию и есть настоящая разница между class и struct в C++; у обоих могут быть методы, конструкторы и спецификаторы доступа.
Что делает ключевое слово friend в C++?
friend предоставляет одной конкретной функции или классу доступ к вашим членам private и protected. Это намеренное, узкое исключение из инкапсуляции — класс прямо называет, кому он доверяет, поэтому доступ никогда не предоставляется неявно.