Справочное руководство по C++ - Бьярн Страустрап
Шрифт:
Интервал:
Закладка:
int b;
protected:
int c;
};
class D: private B {
public:
B::a; // описать `a' как общий член D
B::b; // ошибка: попытка расширить доступ,
// `b' не может быть общим членом D
protected:
B::c; // описать `c' как защищенный член D
B::a; // ошибка: попытка сузить доступ,
// `a' не может быть защищенным членом D
};
Описание доступа для имени перегруженной функции устанавливает доступ в базовом классе ко всем функциям с этим именем, например:
class X {
public:
f();
f(int);
};
class Y: private X {
public:
X::f; // makes X::f() and X::f(int) public in Y
};
Нельзя в производном классе установить доступ к члену базового класса, если в производном классе определен член с этим же именем, например:
class X {
public:
void f();
};
class Y: private X {
public:
void f(int);
X::f; // ошибка: два описания f
};
R.11.4 Друзья
Другом класса называется функция, которая не является членом класса, но в которой можно использовать частные и защищенные члены этого класса. Имя друга не принадлежит области видимости класса, и дружественная функция не вызывается с помощью операций доступа к членам (§R.5.2.4), если только она не является членом другого класса. Следующий пример показывает различие между членами и друзьями:
class X {
int a;
friend void friend_set(X*, int);
public:
void member_set(int);
};
void friend_set(X* p, int i) {p-›a = i;}
void X::member_set(int i) {a = i;}
void f()
{
X obj;
friend_set(&obj,10);
obj.member_set(10);
}
Если в описании friend использовано имя перегруженной функции или операции, только функция, однозначно определяемая типами формальных параметров, становится другом. Функция-член класса X может быть другом класса Y, например:
class Y {
friend char* X::foo(int);
//…
};
Можно объявить все функции класса X друзьями класса Y с помощью спецификации-сложного-типа (§R.9.1):
class Y {
friend class X;
//…
};
Описание одного класса как друг другого класса дополнительно подразумевает, что частные и защищенные члены класса, предлагающего дружбу, могут использоваться в классе, получающем ее, например:
class X {
enum { a=100 };
friend class Y;
};
class Y {
int v[X::a]; // Y друг класса X
};
class Z {
int v[X::a]; // ошибка: X::a недоступно
};
Если класс или функция, объявленные как друзья, не были описаны, их имена попадают в ту же область видимости, что и имя класса, содержащего описание friend (§R.9.1).
Функция, появившаяся первый раз в описании friend, считается эквивалентной функции, описанной как extern (§R.3.3, §R.7.1.1).
Если функция-друг определена в описании класса, она считается функцией со спецификацией inline и к ней применимо правило переноса определения функции для функций-членов (§R.9.3.2). Функция-друг, определенная в описании класса, относится на лексическом уровне к области видимости этого класса. Для функции-друга, определенной вне класса, это не так.
На описание friend не влияет указание спецификаций-доступа (§R.9.2).
Понятие дружбы не является ни наследуемым, ни транзитивным.
Подтвердим это примером:
class A {
friend class B;
int a;
};
class B {
friend class C;
};
class C {
void f(A* p);
{
p-›a++; // ошибка: C не друг класса A, хотя
// является другом друга класса A
}
};
class D: public B {
void f(A* p)
{
p-›a++; // ошибка: D не друг класса A, хотя
// является производным друга класса A
}
};
R.11.5 Доступ к защищенным членам
Друг или функция-член производного класса имеет доступ к защищенному статическому члену базового класса. Друг или функция-член производного класса могут получить доступ к защищенному нестатическому члену одного из своих базовых классов только через указатель, ссылку или объект производного класса (или любого класса, являющегося производным по отношению к нему). Рассмотрим пример:
class B {
protected:
int i;
};
class D1: public B {
};
class D2: public B {
friend void fr(B*, D1*, D2*);
void mem(B*, D1*);
};
void fr(B* pb, D1* p1, D2* p2)
{
pb-›i = 1; // недопустимо
p1-›i = 2; // недопустимо
p2-›i = 3; // нормально (обращение через D2)
}
void D2::mem(B* pb, D1* p1)
{
pb-›i = 1; // недопустимо
p1-›i = 2; // недопустимо
i = 3; // нормально (обращение через this)
}
void g(B* pb, D1* p1, D2* p2)
{
pb-›i = 1; // недопустимо
p1-›i = 2; // недопустимо
p2-›i = 3; // недопустимо
}
R.11.6 Доступ к виртуальным функциям
Правила доступа (§R.11) к виртуальной функции определяются ее описанием и на них не влияют правила доступа к к функции, которая позднее будет подавлять ее. Приведем пример:
class B {
public:
virtual f();
};
class D: public B {
private:
f();
};
void f()
{
D d;
B* pb = &d;
D* pd = &d;
pb-›f(); // нормально: B::f() общий член
// вызывается D::f()
pd-›f(); // ошибка: D::f() частный член
}
Права доступа проверяются при самом вызове, используя тип выражения, обозначающее объект, для которого вызывается функция-член (в примере выше это B*). Доступ к функции-члену в классе, где она определена (D в примере выше), в общем случае неизвестен.
R.11.7 Множественный доступ
Если добраться до имени можно несколькими путями по графу, задающему множественное наследование, то право доступа этого имени считается максимальным из прав, получаемых на разных путях. Поясним это примером:
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(); } // нормально
};
Поскольку W::f() доступно в C::f() по пути, связанному с общим наследованием из B, обращение является законным.
R.12 Специальные функции-члены
Некоторые функции-члены считаются специальными, поскольку они влияют на то, как объекты класса создаются, копируются и уничтожаются, и как значения одного типа преобразуются в значения другого типа. Часто такие функции вызываются неявно.
Эти функции-члены подчиняются обычным правилам доступа (§R.11). Например, описание конструктора со спецификацией protected гарантирует, что создавать объекты с его помощью смогут только производные классы и друзья.
R.12.1 Конструкторы
Конструктором называется функция-член, имя которой совпадает с именем класса, он используется для построения значений, имеющих тип данного класса. Если в классе есть конструктор, то каждый объект этого класса перед произвольным использованием будет инициализироваться, см. §R.12.6.
Конструктор может вызываться для объекта со спецификацией const или volatile. Сам конструктор нельзя описывать со спецификацией const или volatile (§R.9.3.1). Конструктор также не может иметь спецификацию virtual или static.
Конструкторы не наследуются, однако, стандартные конструкторы и конструкторы копирования при необходимости создаются транслятором (§R.12.8). Такие конструкторы являются общими.
Стандартным конструктором для класса X является такой конструктор класса X, который можно вызывать без параметров. Стандартный конструктор для класса X будет создан только тогда, когда для класса X не описано ни одного конструктора.
Конструктором копирования для класса X называется конструктор, который вызывается для копирования объекта класса X, т.е. вызывается с одним параметром типа X. Например, X::X(const X&) и X::X(X&, int=0) являются конструкторами копирования. Конструктор копирования создается только тогда, когда не описано ни одного конструктора копирования.