Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
int f1() const { return prot_mem; }
};
Спецификатор доступа наследования никак не влияет на возможность членов (и друзей) производного класса обратиться к членам его собственного прямого базового класса. Доступ к членам базового класса контролируется спецификаторами доступа в самом базовом классе. Структуры Pub_Derv и Priv_Derv могут обращаться к защищенному члену prot_mem, но ни одна из них не может обратиться к закрытому члену priv_mem.
Задача спецификатора доступа наследования — контролировать доступ пользователей производного класса, включая другие классы, производные от него, к членам, унаследованным от класса Base:
Pub_Derv d1; // члены, унаследованные от Base, являются открытыми
Priv_Derv d2; // члены, унаследованные от Base, являются закрытыми
d1.pub_mem(); // ok: pub_mem является открытой в производном класс
d2.pub_mem(); // ошибка: pub_mem является закрытой в производном классе
Структуры Pub_Derv и Priv_Derv унаследовали функцию pub_mem(). При открытом наследовании члены сохраняют свой спецификатор доступа. Таким образом, объект d1 может вызвать функцию pub_mem(). В структуре Priv_Derv члены класса Base являются закрытыми; пользователи этого класса не смогут вызвать функцию pub_mem().
Спецификатор доступа наследования, используемый производным классом, также контролирует доступ из классов, унаследованных от этого производного класса:
struct Derived_from_Public : public Pub_Derv {
// ok: Base::prot_mem остается защищенной в Pub_Derv
int use_base() { return prot_mem; }
};
struct Derived_from_Private : public Priv_Derv {
// ошибка: Base::prot_mem является закрытой в Priv_Derv
int use_base() { return prot_mem; }
};
Классы, производные от структуры Pub_Derv, могут обращаться к переменной-члену prot_mem класса Base, поскольку она остается защищенным членом в структуре Pub_Derv. У классов, производных от структуры Priv_Derv, напротив, такого доступа нет. Все члены, которые структура Priv_Derv унаследовала от класса Base, являются закрытыми.
Если бы был определен другой класс, скажем, Prot_Derv, использующий защищенное наследование, открытые члены класса Base в этом классе будут защищенными. У пользователей структуры Prot_Derv не было бы никакого доступа к функции pub_mem(), но ее члены и друзья могли бы обратиться к унаследованному члену.
Доступность преобразования производного класса в базовый классБудет ли доступно преобразование производного класса в базовый класс (см. раздел 15.2.2), зависит от того, какой код пытается использовать преобразование, а также от спецификатора доступа, используемого при наследовании производного класса. С учетом, что класс D происходит от класса B:
• Пользовательский код может использовать преобразование производного класса в базовый, только если класс D открыто наследует класс B. Пользовательский код не может использовать преобразование, если наследование было защищенным или закрытым.
• Функции-члены и друзья класса D могут использовать преобразование в В независимо от вида наследования D от B. Преобразование производного в прямой базовый класс всегда доступно для членов и друзей производного класса.
• Функции-члены и друзья классов, производных от класса D, могут использовать преобразование производного класса в базовый, если наследование было открытым или защищенным. Такой код не сможет использовать преобразование, если наследование классом D класса В было закрытым.
В любом месте кода, где доступен открытый член базового класса, будет доступно также преобразование производного класса в базовый, но не наоборот.
Ключевая концепция. Проект класса и защищенные членыБез наследования у класса будет два разных вида пользователей: обычные пользователи и разработчики (implementor). Обычные пользователи пишут код, который использует объекты типа класса; такой код может обращаться только к открытым членам класса (интерфейсу). Разработчики пишут код, содержащийся в членах и друзьях класса. Члены и друзья класса могут обращаться и к открытым, и к закрытым разделам (реализации).
При наследовании появляется третий вид пользователей, а именно производные классы. Базовый класс делает защищенными те части своей реализации, которые позволено использовать его производным классам. Защищенные члены остаются недоступными обычному пользовательскому коду; закрытые члены остаются недоступными производным классам и их друзьям.
Подобно любому другому классу, базовый класс объявляет члены своего интерфейса открытыми. Класс, используемый как базовый, может разделить свою реализацию на члены, доступные для производных классов и доступные только для базового класса и его друзей. Член класса, относящийся к реализации, должен быть защищен, если он предоставляет функцию или данные, которые производный класс должен будет использовать в собственной реализации. В противном случае члены реализации должны быть закрытыми.
Дружественные отношения и наследованиеПодобно тому, как дружественные отношения не передаются (см. раздел 7.3.4), они также не наследуются. У друзей базового класса нет никаких специальных прав доступа к членам его производных классов, а у друзей производного класса нет специальных прав доступа к базовому классу:
class Base {
// добавлено объявление; другие члены, как прежде
friend class Pal; // у Pal нет доступа к классам, производным от Base
};
class Pal {
public:
int f(Base b) { return b.prot_mem; } // ok: Pal дружествен Base
int f2(Sneaky s) { return s.j; } // ошибка: Pal не
// дружествен Sneaky
// доступ к базовому классу контролируется базовым классом, даже в
// объекте производного
int f3(Sneaky s) { return s.prot_mem; } // ok: Pal дружествен
};
Факт допустимости функции f3() может показаться удивительным, но он непосредственно следует из правила, что все классы контролируют доступ к собственным членам. Класс Pal — друг класса Base, поэтому класс Pal может обращаться к членам объектов класса Base. Это относится и к встроенным в объект класса Base объектам классов, производных от него.
Когда класс объявляет другой класс дружественным, это относится только к данному классу, ни его базовые, ни производные классы никаких специальных прав доступа не имеют:
// у D2 нет доступа к закрытым или защищенным членам Base
class D2 : public Pal {
public:
int mem(Base b)
{ return b.prot_mem; } // ошибка: дружба не наследуется
};
Дружественные отношения не наследуются; каждый класс сам контролирует доступ к своим членам.
Освобождение индивидуальных членовИногда необходимо изменить уровень доступа к имени, унаследованному производным классом. Для этого можно использовать объявление using (см. раздел 3.1):
class Base {
public:
std::size_t size() const { return n; }
protected:
std::size_t n;
};
class Derived : private Base { // заметьте, наследование закрытое
public:
// обеспечить уровни доступа для членов, связанных с размером объекта
using Base::size;
protected:
using Base::n;
};
Поскольку класс Derived использует закрытое наследование, унаследованные члены size() и n по умолчанию будут закрытыми членами класса Derived. Объявления using корректируют доступность этих членов. Пользователи класса Derived могут обращаться к функции-члену size(), а классы, впоследствии произошедшие от класса Derived, смогут обратиться к переменной n.
Объявление using в классе может использовать имя любого доступного (не закрытого) члена прямого или косвенного базового класса. Доступность имени, указанного в объявлении using, зависит от спецификатора доступа, предшествующего объявлению using. Таким образом, если объявление using расположено в разделе private класса, то имя будет доступно только для членов и друзей. Если объявление находится в разделе public, имя доступно для всех пользователей класса. Если объявление находится в разделе protected, имя доступно только для членов, друзей и производных классов.