Спецификаторы доступа
В спецификации-элемента класса/структуры или объединения определяют доступность последующих элементов.
В спецификаторе-базы производного класса определяют доступность унаследованных элементов последующего базового класса.
Синтаксис
public : объявления-элементов
|
(1) | ||||||||
protected : объявления-элементов
|
(2) | ||||||||
private : объявления-элементов
|
(3) | ||||||||
public базовый-класс
|
(4) | ||||||||
protected базовый-класс
|
(5) | ||||||||
private базовый-класс
|
(6) | ||||||||
Закрытые элементы базового класса всегда недоступны производному классу, независимо от открытого, защищённого или закрытого наследования.
Объяснение
Имя каждого элемента класса (статического, нестатического, функции, типа и т.д.) имеет связанный с ним "доступ к элементу". Когда имя элемента используется где-либо в программе, проверяется его доступ, и если он не соответствует правилам доступа, программа не компилируется:
#include <iostream>
class Example
{
public: // все объявления после этой точки являются открытыми
void add(int x) // элемент "add" имеет открытый доступ
{
n += x; // OK: закрытый Example::n доступен из Example::add
}
private: // все объявления после этой точки являются закрытыми
int n = 0; // элемент "n" имеет закрытый доступ
};
int main()
{
Example e;
e.add(1); // OK: открытый Example::add доступен из main
// e.n = 7; // ошибка: закрытый Example::n недоступен из main
}
Спецификаторы доступа дают автору класса возможность решать, какие элементы класса доступны для пользователей класса (то есть интерфейс), а какие для внутреннего использования класса (реализация)
В деталях
Все элементы класса (тела функций-элементов, инициализаторы объектов-элементов и все определения вложенных классов) имеют доступ ко всем именам, к которым может получить доступ класс. Локальный класс внутри функции-элемента имеет доступ ко всем именам, к которым может получить доступ функция-элемент.
Класс, определённый с помощью ключевого слова class, по умолчанию имеет закрытый доступ для своих элементов и базовых классов. Класс, определённый с помощью ключевого слова struct, по умолчанию имеет открытый доступ для своих элементов и базовых классов. По умолчанию union имеет открытый доступ для своих элементов.
Чтобы предоставить доступ дополнительным функциям или классам к защищённым или закрытым элементам, можно использовать дружественное объявление.
Доступность применяется ко всем именам, независимо от их происхождения, поэтому проверяется имя, введённое typedef или using объявлениями (за исключением наследованных конструкторов), а не имя, на которе оно ссылается:
class A : X
{
class B {}; // B является закрытым в A
public:
typedef B BB; // BB является открытым
};
void f()
{
A::B y; // ошибка: A::B является закрытым
A::BB x; // OK: A::BB является открытым
}
Доступ к элементам не влияет на видимость: имена закрытых и наследуемых закрытым образом элементов видны и учитываются при разрешении перегрузки, неявные преобразования в недоступные базовые классы по-прежнему учитываются и т.д. Проверка доступа к элементам это последний шаг после интерпретации любой заданной языковой конструкции. Смысл этого правила в том, что замена любых private на public никогда не меняет поведение программы.
Проверка доступа для имён, используемых в аргументах функции по умолчанию, а также в параметрах шаблона по умолчанию выполняется в момент объявления, а не в момент использования.
Правила доступа для имён виртуальных функций проверяются в точке вызова с использованием типа выражения, используемого для обозначения объекта, для которого вызывается функция-элемент. Доступ последнего переопределения игнорируется:
struct B
{
virtual int f(); // f является открытой в B
};
class D : public B
{
private: int f(); // f является закрытой в D
};
void f()
{
D d;
B& b = d;
b.f(); // OK: B::f является открытой, D::f вызывается, даже если она закрытая
d.f(); // ошибка: D::f является закрытой
}
Имя, которое является закрытым в соответствии с неполным поиском имени, может быть доступно через поиск по полному имени:
class A {};
class B : private A {};
class C : public B
{
A* p; // ошибка: поиск неквалифицированного имени находит A как закрытый
// базовый класс для B
::A* q; // ОК: поиск квалифицированного имени находит объявление на уровне
// пространства имён
};
Имя, доступное через несколько путей в графе наследования, имеет доступ к пути с наибольшим доступом:
class W
{
public:
void f();
};
class A : private virtual W {};
class B : public virtual W {};
class C : public A, public B
{
void f()
{
W::f(); // OK: W доступен для C через B
}
};
В классе может появляться любое количество спецификаторов доступа в любом порядке. Спецификаторы доступа к элементам могут влиять на компоновку класса: адреса нестатических элементов данных гарантированно располагаются только в порядке объявления для элементов , не разделённых спецификатором доступа (до C++11)с тем же доступом (начиная с C++11).
|
Для типов стандартной компановки все нестатические элементы данных должны иметь одинаковый доступ. |
(начиная с C++11) |
Когда элемент повторно объявляется в том же классе, он должен делать это с тем же доступом к элементу:
struct S
{
class A; // S::A является открытым
private:
class A {}; // ошибка: нельзя изменить доступ
};
Открытый доступ к элементам
Открытые элементы образуют часть открытого интерфейса класса (остальные части открытого интерфейса это функции, не являющиеся элементнами, найденные с помощью ADL).
Открытый элемент класса доступен в любом месте:
class S
{
public:
// n, E, A, B, C, U, f являются открытыми элементами
int n;
enum E {A, B, C};
struct U {};
static void f() {}
};
int main()
{
S::f(); // S::f доступна в main
S s;
s.n = S::B; // S::n и S::B доступны в main
S::U x; // S::U доступна в main
}
Защищённый доступ к элементам
Защищённые элементы образуют интерфейс класса с его производными классами (который отличается от открытого интерфейса класса).
Защищённый элемент класса доступен только
struct Base
{
protected:
int i;
private:
void g(Base& b, struct Derived& d);
};
struct Derived : Base
{
void f(Base& b, Derived& d) // функция-элемент производного класса
{
++d.i; // OK: тип d является Derived
++i; // OK: тип подразумеваемого '*this' является Derived
// ++b.i; // ошибка: не удается получить доступ к защищённому
// элементу через Base (в противном случае можно было
// бы изменить базовую реализацию других производных
// классов, таких как гипотетический Derived2)
}
};
void Base::g(Base& b, Derived& d) // функция-элемент Base
{
++i; // OK
++b.i; // OK
++d.i; // OK
}
void x(Base& b, Derived& d) // не элемент не друг
{
// ++b.i; // ошибка: нет доступа из не элемента
// ++d.i; // ошибка: нет доступа из не элемента
}
Когда формируется указатель на защищённый элемент, он должен использовать производный класс в своём объявлении:
struct Base
{
protected:
int i;
};
struct Derived : Base
{
void f()
{
// int Base::* ptr = &Base::i; // ошибка: необходимо указать имя, используя Derived
int Base::* ptr = &Derived::i; // OK
}
};
Закрытый доступ к элементам
Закрытые элементы формируют реализацию класса, а также закрытый интерфейс для других элементов класса.
Закрытый элемент класса доступен только элементам и друзьям этого класса, независимо от того, находятся ли элементы в одном или разных экземплярах:
class S
{
private:
int n; // S::n является закрытым
public:
S() : n(10) {} // this->n доступен в S::S
S(const S& other) : n(other.n) {} // other.n доступен в S::S
};
Явное приведение (в стиле C или в стиле функции) позволяет выполнять приведение из производного lvalue к ссылке на его закрытый базовый класс или из указателя на производный класс к указателю на его закрытый базовый класс.
Наследование
Смотрите производные классы, чтобы узнать о значении открытого, защищённого и закрытого наследования.
Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
| Номер | Применён | Поведение в стандарте | Корректное поведение |
|---|---|---|---|
| CWG 1873 | C++98 | защищённые элементы были доступны друзьям производных классов | сделаны недоступными |