Spec-Zone.ru › C++

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

В member-specification класса/структуры или объединения задают доступность последующих членов.

В base-specifier объявления производного класса задают доступность унаследованных членов последующего базового класса.

Синтаксис

public : member-declarations (1)
protected : member-declarations (2)
private : member-declarations (3)
public base-class (4)
protected base-class (5)
private base-class (6)
1) Члены, объявленные после спецификатора доступа, имеют открытый доступ.
2) Члены, объявленные после спецификатора доступа, имеют защищённый доступ.
3) Члены, объявленные после спецификатора доступа, имеют закрытый доступ.
4) Открытое наследование: открытый и защищённый доступ к членам базового класса, указанным после спецификатора доступа, сохраняется в производном классе.
5) Защищённое наследование: члены базового класса с открытым и защищённым доступом, указанные после спецификатора доступа, становятся защищёнными членами производного класса.
6) Закрытое наследование: члены базового класса с открытым и защищённым доступом, указанные после спецификатора доступа, становятся закрытыми членами производного класса.

Закрытые члены базового класса всегда недоступны производному классу независимо от того, используется открытое, защищённое или закрытое наследование.

Пояснение

С каждым именем члена класса (статическим, нестатическим, функцией, типом и т. д.) связан определённый «доступ к члену». При использовании имени члена в любой части программы проверяется его доступность, и если она не соответствует правилам доступа, программа не компилируется:

#include <iostream>

class Example
{
public:             // all declarations after this point are public
    void add(int x) // member "add" has public access
    {
        n += x;     // OK: private Example::n can be accessed from Example::add
    }
private:            // all declarations after this point are private
    int n = 0;      // member "n" has private access
};

int main()
{
    Example e;
    e.add(1); // OK: public Example::add can be accessed from main
//  e.n = 7;  // error: private Example::n cannot be accessed from main
}

Спецификаторы доступа позволяют автору класса определять, какие члены класса доступны его пользователям (то есть составляют интерфейс), а какие предназначены для внутреннего использования в классе (то есть составляют реализацию).

Подробно

Все объявления членов класса (включая определения вне класса) имеют доступ ко всем именам, доступным самому классу. Локальный класс внутри функции-члена имеет доступ ко всем именам, доступным этой функции-члену.

Для класса, объявленного с ключевым словом class, по умолчанию установлен закрытый доступ к его членам и базовым классам. Для класса, объявленного с ключевым словом struct, по умолчанию установлен открытый доступ к его членам и базовым классам. Для объединения по умолчанию установлен открытый доступ к его членам.

Чтобы предоставить дополнительным функциям или классам доступ к защищённым или закрытым членам, можно использовать объявление дружественности.

Проверка доступности применяется ко всем именам независимо от их происхождения, поэтому проверяется имя, введённое с помощью typedef или объявления using (за исключением наследуемых конструкторов), а не имя, на которое оно ссылается:

class A : X
{
    class B {};   // B is private in A
public:
    typedef B BB; // BB is public
};

void f()
{
    A::B y;  // error: A::B is private
    A::BB x; // OK: A::BB is public
}

Доступ к члену не влияет на видимость: имена закрытых и унаследованных закрытым образом членов видимы и учитываются при разрешении перегрузки, неявные преобразования к недоступным базовым классам по-прежнему учитываются и т. д. Проверка доступа к члену выполняется последним шагом после интерпретации соответствующей языковой конструкции. Смысл этого правила в том, что замена любого private на public никогда не изменяет поведение программы.

Проверка доступа к именам, используемым в аргументах функции по умолчанию, а также в параметрах шаблона по умолчанию выполняется в точке объявления, а не в точке использования.

Правила доступа к именам виртуальных функций проверяются в точке вызова с учётом типа выражения, обозначающего объект, для которого вызывается функция-член. Доступность конечной функции-переопределителя не учитывается:

struct B
{
    virtual int f(); // f is public in B
};

class D : public B
{
private:
    int f(); // f is private in D
};

void f()
{
    D d;
    B& b = d;
    
    b.f(); // OK: B::f is public, D::f is invoked even though it's private
    d.f(); // error: D::f is private
}

Имя, закрытое при неквалифицированном поиске имени, может быть доступно при поиске квалифицированного имени:

class A {};

class B : private A {};

class C : public B
{
    A* p;   // error: unqualified name lookup finds A as the private base of B
    ::A* q; // OK: qualified name lookup finds the namespace-level declaration
};

Имя, доступное по нескольким путям в графе наследования, имеет доступность наиболее доступного пути:

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 is accessible to C through B
    }
};

Внутри класса может находиться любое количество спецификаторов доступа в любом порядке.

Спецификаторы доступа к членам могут влиять на расположение класса в памяти: гарантируется, что адреса нестатических членов-данных увеличиваются в порядке объявления только для членов, не разделённых спецификатором доступа(до C++11)имеющих одинаковый уровень доступа(начиная с C++11).

(до C++23)

У типов со стандартным расположением все нестатические члены-данные должны иметь одинаковый уровень доступа.

(начиная с C++11)

При повторном объявлении члена в том же классе необходимо использовать тот же уровень доступа:

struct S
{
    class A;    // S::A is public
private:
    class A {}; // error: cannot change access
};

Открытый доступ к членам

Открытые члены составляют часть открытого интерфейса класса (другую часть открытого интерфейса составляют функции, не являющиеся членами, найденные с помощью ADL).

Открытый член класса доступен в любом месте:

class S
{
public:
    // n, E, A, B, C, U, f are public members
    int n;
    enum E {A, B, C};
    struct U {};
    static void f() {}
};

int main()
{
    S::f();     // S::f is accessible in main
    
    S s;
    s.n = S::B; // S::n and S::B are accessible in main
    
    S::U x;     // S::U is accessible in main
}

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

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

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

1) членам и друзьям этого класса;
2) членам и друзьям любого производного класса этого класса, но только если класс объекта, через который осуществляется доступ к защищённому члену, является этим производным классом или классом, производным от него:
struct Base
{
protected:
    int i;
private:
    void g(Base& b, struct Derived& d);
};

struct Derived : Base
{
    friend void h(Base& b, Derived& d);
    void f(Base& b, Derived& d) // member function of a derived class
    {
        ++d.i;                  // OK: the type of d is Derived
        ++i;                    // OK: the type of the implied '*this' is Derived
//      ++b.i;                  // error: can't access a protected member through
                                // Base (otherwise it would be possible to change
                                // other derived classes, like a hypothetical
                                // Derived2, base implementation)
    }
};
 
void Base::g(Base& b, Derived& d) // member function of Base
{
    ++i;                          // OK
    ++b.i;                        // OK
    ++d.i;                        // OK
}

void h(Base& b, Derived& d) // Friend of Derived
{
    ++d.i;                  // OK: friend of Derived can access a protected 
                            // member through an object of Derived
//  ++b.i;                  // error: friend of Derived is not a friend of Base
}

void x(Base& b, Derived& d) // non-member non-friend
{
//  ++b.i;                  // error: no access from non-member
//  ++d.i;                  // error: no access from non-member
}

При создании указателя на защищённый член в его объявлении необходимо использовать производный класс:

struct Base
{
protected:
    int i;
};

struct Derived : Base
{
    void f()
    {
//      int Base::* ptr = &Base::i;    // error: must name using Derived
        int Base::* ptr = &Derived::i; // OK
    }
};

Закрытый доступ к членам

Закрытые члены составляют реализацию класса, а также закрытый интерфейс для остальных членов класса.

Закрытый член класса доступен только членам и друзьям этого класса независимо от того, принадлежат ли они одному и тому же или разным экземплярам:

class S
{
private:
    int n; // S::n is private
public:
    S() : n(10) {}                    // this->n is accessible in S::S
    S(const S& other) : n(other.n) {} // other.n is accessible in S::S
};

Явное приведение (в стиле C и в функциональном стиле) позволяет преобразовать производное lvalue в ссылку на его закрытый базовый класс или указатель на производный класс в указатель на его закрытый базовый класс.

Наследование

Значение открытого, защищённого и закрытого наследования описано в разделе о производных классах.

Ключевые слова

public, protected, private

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.

DR Применено к Опубликованное поведение Правильное поведение
CWG 1873 C++98 защищённые члены были доступны друзьям производных классов сделаны недоступными

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/access

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API