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

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

Материал из cppreference.com
< cpp‎ | language
 
 
 
 

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

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

Содержание

[править] Синтаксис

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 1873C++98защищённые элементы были доступны друзьям производных классовсделаны недоступными