close
Пространства имён
Варианты
Действия

Спецификаторы доступа

Материал из cppreference.com
 
 
 
 

В спецификации-элемента класса/структуры или объединения определяют доступность последующих элементов.

В спецификаторе-базы производного класса определяют доступность унаследованных элементов последующего базового класса.

Синтаксис

public : объявления-элементов (1)
protected : объявления-элементов (2)
private : объявления-элементов (3)
public базовый-класс (4)
protected базовый-класс (5)
private базовый-класс (6)
1) Элементы, объявленные после спецификатора доступа, имеют открытый доступ к элементам.
2) Элементы, объявленные после спецификатора доступа, имеют защищённый доступ к элементам.
3) Элементы, объявленные после спецификатора доступа, имеют закрытый доступ к элементам.
4) Открытое наследование: открытые и защищённые элементы базового класса, перечисленные после спецификатора доступа, сохраняют доступ к своим элементам в производном классе.
5) Защищённое наследование: открытые и защищённые элементы базового класса, перечисленные после спецификатора доступа, являются защищёнными элементами производного класса.
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
}

Защищённый доступ к элементам

Защищённые элементы образуют интерфейс класса с его производными классами (который отличается от открытого интерфейса класса).

Защищённый элемент класса доступен только

1) элементам и друзьям этого класса;
2) элементам любого производного класса этого класса, но только тогда, когда класс объекта, через который осуществляется доступ к защищённому элементу, является этим производным классом или производным классом этого производного класса:
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 защищённые элементы были доступны друзьям производных классов сделаны недоступными