Разница между частным, публичным и защищенным наследством
В чем разница между public
, private
, а также protected
наследование в C++? Все вопросы, которые я нашел на SO, касаются конкретных случаев.
17 ответов
Чтобы ответить на этот вопрос, я хотел бы сначала описать методы доступа участников в моих собственных словах. Если вы уже знаете это, перейдите к заголовку "следующий:".
Есть три метода доступа, о которых я знаю: public
, protected
а также private
,
Позволять:
class Base {
public:
int publicMember;
protected:
int protectedMember;
private:
int privateMember;
};
- Все, что известно
Base
также знает, чтоBase
содержитpublicMember
, - Только дети (и их дети) знают, что
Base
содержитprotectedMember
, - Никто кроме
Base
знает оprivateMember
,
Под "осведомлен" я имею в виду "признать существование и, таким образом, иметь возможность доступа".
следующий:
То же самое происходит с публичным, частным и защищенным наследством. Давайте рассмотрим класс Base
и класс Child
что наследует от Base
,
- Если наследство
public
все, что знаетBase
а такжеChild
также знает, чтоChild
наследуется отBase
, - Если наследство
protected
, толькоChild
и его дети знают, что они наследуют отBase
, - Если наследство
private
никто кромеChild
знает о наследстве.
class A
{
public:
int x;
protected:
int y;
private:
int z;
};
class B : public A
{
// x is public
// y is protected
// z is not accessible from B
};
class C : protected A
{
// x is protected
// y is protected
// z is not accessible from C
};
class D : private A // 'private' is default for classes
{
// x is private
// y is private
// z is not accessible from D
};
ВАЖНОЕ ПРИМЕЧАНИЕ: все классы B, C и D содержат переменные x, y и z. Это просто вопрос доступа.
Об использовании защищенного и частного наследования вы можете прочитать здесь.
Ограничение видимости наследования сделает код неспособным увидеть, что какой-то класс наследует другой класс: неявные преобразования из производного в базовый не будут работать, и static_cast
от базы до производной тоже не подойдет.
Только члены / друзья класса могут видеть личное наследование, и только члены / друзья и производные классы могут видеть защищенное наследование.
общественное наследство
ИС-А наследство. Кнопка - это окно, и везде, где требуется окно, кнопка также может быть передана.
class button : public window { };
защищенное наследство
Защищено реализовано в сроках. Редко полезно. Используется в
boost::compressed_pair
чтобы извлечь из пустых классов и сохранить память, используя пустую оптимизацию базового класса (пример ниже не использует шаблон, чтобы оставаться в точке):struct empty_pair_impl : protected empty_class_1 { non_empty_class_2 second; }; struct pair : private empty_pair_impl { non_empty_class_2 &second() { return this->second; } empty_class_1 &first() { return *this; // notice we return *this! } };
частное наследство
Реализована-в-ухудшений условий. Использование базового класса только для реализации производного класса. Полезно с чертами и если размер имеет значение (пустые черты, которые содержат только функции, будут использовать пустую оптимизацию базового класса). Часто сдерживание - лучшее решение, все же. Размер строк имеет решающее значение, поэтому здесь его часто используют.
template<typename StorageModel> struct string : private StorageModel { public: void realloc() { // uses inherited function StorageModel::realloc(); } };
публичный член
заполнитель
class pair { public: First first; Second second; };
Accessors
class window { public: int getWidth() const; };
защищенный член
Предоставление расширенного доступа для производных классов
class stack { protected: vector<element> c; }; class window { protected: void registerClass(window_descriptor w); };
частный член
Сохранить детали реализации
class window { private: int width; };
Обратите внимание, что приведения в стиле C преднамеренно позволяют приводить производный класс к защищенному или частному базовому классу определенным и безопасным способом, а также приводить в другом направлении. Этого следует избегать любой ценой, потому что это может сделать код зависимым от деталей реализации - но при необходимости вы можете использовать эту технику.
Эти три ключевых слова также используются в совершенно другом контексте для определения модели наследования видимости.
В этой таблице собраны все возможные комбинации объявления компонента и модели наследования, представляющие итоговый доступ к компонентам, когда подкласс полностью определен.
Таблица выше интерпретируется следующим образом (взгляните на первый ряд):
если компонент объявлен как открытый, а его класс наследуется как открытый, результирующий доступ является открытым.
Пример:
class Super {
public: int p;
private: int q;
protected: int r;
};
class Sub : private Super {};
class Subsub : public Sub {};
Полученный доступ к переменным p
, q
, r
в классе Subsub нет ни одного.
Другой пример:
class Super {
private: int x;
protected: int y;
public: int z;
};
class Sub : protected Super {};
Полученный доступ к переменным y
, z
в классе Sub защищен и для переменных x
нет
Более подробный пример:
class Super {
private:
int storage;
public:
void put(int val) { storage = val; }
int get(void) { return storage; }
};
int main(void) {
Super object;
object.put(100);
object.put(object.get());
cout << object.get() << endl;
return 0;
}
Теперь давайте определим подкласс:
class Sub : Super { };
int main(void) {
Sub object;
object.put(100);
object.put(object.get());
cout << object.get() << endl;
return 0;
}
Определенный класс с именем Sub, который является подклассом класса с именем Super
или это Sub
класс является производным от Super
учебный класс. Sub
Класс не вводит ни новых переменных, ни новых функций. Означает ли это, что любой объект Sub
класс наследует все черты после Super
класс на самом деле является копией Super
объекты класса?
Нет Это не так.
Если мы скомпилируем следующий код, мы получим только ошибки компиляции, говорящие, что put
а также get
методы недоступны. Зачем?
Когда мы опускаем спецификатор видимости, компилятор предполагает, что мы собираемся применить так называемое частное наследование. Это означает, что все общедоступные компоненты суперкласса превращаются в закрытый доступ, приватные компоненты суперкласса вообще не будут доступны. Следовательно, это означает, что вы не можете использовать последний внутри подкласса.
Мы должны сообщить компилятору, что мы хотим сохранить ранее использованную политику доступа.
class Sub : public Super { };
Не вводите в заблуждение: это не означает, что частные компоненты класса Super (например, переменная хранения) превратятся в публичные в некотором роде волшебным образом. Частные компоненты останутся частными, публичные останутся публичными.
Объекты Sub
класс может делать "почти" те же вещи, что и их старшие братья и сестры, созданные из Super
учебный класс. "Почти", потому что сам факт подкласса также означает, что класс потерял доступ к закрытым компонентам суперкласса. Мы не можем написать функцию-член Sub
класс, который сможет напрямую манипулировать переменной хранения.
Это очень серьезное ограничение. Есть ли обходной путь?
Да
Третий уровень доступа называется защищенным. Ключевое слово protected означает, что помеченный им компонент ведет себя как общедоступный, когда используется любым из подклассов, и выглядит как частный для остального мира. - Это верно только для публично унаследованных классов (например, суперкласса в нашем примере) -
class Super {
protected:
int storage;
public:
void put(int val) { storage = val; }
int get(void) { return storage; }
};
class Sub : public Super {
public:
void print(void) {cout << "storage = " << storage;}
};
int main(void) {
Sub object;
object.put(100);
object.put(object.get() + 1);
object.print();
return 0;
}
Как вы видите в примере кода, мы получили новую функциональность Sub
класс, и он делает одну важную вещь: он обращается к переменной хранения из класса Super.
Это было бы невозможно, если бы переменная была объявлена как приватная. В области видимости основной функции переменная все равно остается скрытой, так что если вы напишите что-то вроде:
object.storage = 0;
Компилятор сообщит вам, что это error: 'int Super::storage' is protected
,
Наконец, последняя программа выдаст следующий вывод:
storage = 101
Это связано с тем, как открытые члены базового класса выставляются из производного класса.
- public -> публичные члены базового класса будут публичными (обычно по умолчанию)
- protected -> публичные члены базового класса будут защищены
- private -> публичные члены базового класса будут приватными
Как указывает Литб, публичное наследование - это традиционное наследование, которое вы увидите в большинстве языков программирования. То есть он моделирует отношения "IS-A". Частное наследование, то, что AFAIK свойственно C++, является отношением "РЕАЛИЗОВАНО В УСЛОВИЯХ". То есть вы хотите использовать открытый интерфейс в производном классе, но не хотите, чтобы пользователь производного класса имел доступ к этому интерфейсу. Многие утверждают, что в этом случае вы должны агрегировать базовый класс, то есть вместо того, чтобы базовый класс был закрытым, сделать член в производном для повторного использования функциональности базового класса.
Member in base class : Private Protected Public
Тип наследования: Объект, унаследованный как:
Private : Inaccessible Private Private
Protected : Inaccessible Protected Protected
Public : Inaccessible Protected Public
1) Общественное наследство:
а. Частные члены Базового класса не доступны в производном классе.
б. Защищенные члены базового класса остаются защищенными в производном классе.
с. Открытые члены Базового класса остаются публичными в производном классе.
Таким образом, другие классы могут использовать открытые члены класса Base через объект класса Derived.
2) Защищенное наследование:
а. Частные члены Базового класса не доступны в производном классе.
б. Защищенные члены базового класса остаются защищенными в производном классе.
с. Открытые члены Базового класса тоже становятся защищенными членами Производного класса.
Таким образом, другие классы не могут использовать публичные члены класса Base через объект класса Derived; но они доступны для подкласса Derived.
3) Частное наследство:
а. Частные члены Базового класса не доступны в производном классе.
б. Защищенные и публичные члены Базового класса становятся частными членами Производного класса.
Таким образом, никакие члены базового класса не могут быть доступны другим классам через объект класса Derived, поскольку они являются частными в классе Derived. Таким образом, даже подкласс класса Derived не может получить к ним доступ.
Публичное наследование моделирует отношения IS-A. С
class B {};
class D : public B {};
каждый D
это B
,
Частное наследование моделирует отношения IS-IMPLEMENTED-USING (или как там это называется). С
class B {};
class D : private B {};
D
это не B
, но каждый D
использует его B
в его реализации. Частное наследование всегда можно устранить, используя вместо этого сдерживание:
class B {};
class D {
private:
B b_;
};
это D
тоже можно реализовать используя B
в этом случае используя его b_
, Сдерживание - это менее тесная связь между типами, чем наследование, поэтому в целом оно должно быть предпочтительным. Иногда использование сдерживания вместо частного наследования не так удобно, как частное наследование. Часто это слабое оправдание лени.
Я не думаю, что кто-нибудь знает, что protected
модели наследования. По крайней мере, я еще не видел убедительного объяснения.
Если вы публично наследуете от другого класса, все знают, что вы наследуете, и вы можете полиморфно использоваться кем угодно через указатель базового класса.
Если вы наследуете защищённо, только ваши дети смогут использовать вас полиморфно.
Если вы наследуете конфиденциально, только вы сами сможете выполнять методы родительского класса.
Что в основном символизирует знания остальных классов о ваших отношениях с родительским классом
Accessors | Base Class | Derived Class | World
—————————————+————————————+———————————————+———————
public | y | y | y
—————————————+————————————+———————————————+———————
protected | y | y | n
—————————————+————————————+———————————————+———————
private | | |
or | y | n | n
no accessor | | |
y: accessible
n: not accessible
Основываясь на этом примере для Java... Я думаю, что столик стоит тысячу слов:)
Доступ к защищенным членам данных может получить любой класс, унаследованный от вашего класса. Частные данные участников, однако, не могут. Допустим, у нас есть следующее:
class MyClass {
private:
int myPrivateMember; // lol
protected:
int myProtectedMember;
};
Из вашего расширения в этот класс, ссылаясь this.myPrivateMember
не сработает Тем не мение, this.myProtectedMember
будут. Значение все еще инкапсулировано, поэтому, если у нас есть экземпляр этого класса с именем myObj
, затем myObj.myProtectedMember
не будет работать, поэтому он похож по функции на частный элемент данных.
Я попытался объяснить наследование, используя картинку ниже.
Основная суть заключается в том, что частные члены родительского класса никогда не доступны напрямую из производного / дочернего класса, но вы можете использовать функцию члена родительского класса для доступа к закрытым членам родительского класса. Частные переменные всегда присутствуют в производном классе, но не могут быть доступны производному классу. Это похоже на их, но вы не можете видеть своими глазами, но если вы спросите кого-нибудь из родительского класса, он сможет вам это описать.
Резюме:
- Частный: никто не может видеть это, кроме как в классе
- Защищено: частные + производные классы могут видеть это
- Общественность: мир может это увидеть
При наследовании вы можете (на некоторых языках) изменить тип защиты элемента данных в определенном направлении, например, с защищенного на общедоступный.
Частный:
Закрытые члены базового класса могут быть доступны только членам этого базового класса.
Общественность:
К открытым членам базового класса могут обращаться члены этого базового класса, члены его производного класса, а также члены, которые находятся за пределами базового класса и производного класса.
Защищено:
Защищенные члены базового класса могут быть доступны как членам базового класса, так и членам его производного класса.
Короче:
частный: база
защищенный: базовый + производный
public: base + производная + любой другой участник
Я нашел простой ответ и подумал о том, чтобы опубликовать его и для дальнейшего использования.
Это из ссылок http://www.learncpp.com/cpp-tutorial/115-inheritance-and-access-specifiers/
class Base
{
public:
int m_nPublic; // can be accessed by anybody
private:
int m_nPrivate; // can only be accessed by Base member functions (but not derived classes)
protected:
int m_nProtected; // can be accessed by Base member functions, or derived classes.
};
class Derived: public Base
{
public:
Derived()
{
// Derived's access to Base members is not influenced by the type of inheritance used,
// so the following is always true:
m_nPublic = 1; // allowed: can access public base members from derived class
m_nPrivate = 2; // not allowed: can not access private base members from derived class
m_nProtected = 3; // allowed: can access protected base members from derived class
}
};
int main()
{
Base cBase;
cBase.m_nPublic = 1; // allowed: can access public members from outside class
cBase.m_nPrivate = 2; // not allowed: can not access private members from outside class
cBase.m_nProtected = 3; // not allowed: can not access protected members from outside class
}
По сути, это защита доступа к открытым и защищенным членам базового класса в производном классе. При публичном наследовании производный класс может видеть открытых и защищенных членов базы. С частным наследством это не может. С защищенным, производным классом и любыми производными классами, которые могут их видеть.
Наследование
- public: ничего, то же, что и базовый класс.
- protected: Public -> protected, в остальном то же самое.
- частный (по умолчанию): общедоступный и защищенный -> частный (теперь все конфиденциально!)
Затем важно различать, как объект класса выглядит снаружи и изнутри.
Изнутри класса : вы можете получить доступ ко всем полям и методам из базы, которые не являютсяprivate
. Тип наследования здесь не имеет значения.
Извне : вы можете получить доступ только к общедоступным методам и полям класса. Это означает, что интерфейс базы должен быть общедоступным , а также публично наследоваться производными , чтобы свойства доступа передавались через него.
Как насчетprotected
?
Защищено по сути означает: доступно не снаружи, а изнутри.
Вот код, чтобы продемонстрировать это. Все прокомментированное не скомпилируется:
#include <iostream>
struct base {
private:
int a;
auto get_a() {return a; }
protected:
int b;
auto get_b() {return b; }
public:
int c;
auto get_c() {return c; }
};
struct derive_private : private base {
/* From inside */
auto print() {
// a = 3;
b = 2;
c = 3;
// std::cout << get_a() << std::endl;
std::cout << get_b() << std::endl;
std::cout << get_c() << std::endl;
}
};
struct derive_protected : protected base {
/* From inside */
auto print() {
// a = 3;
b = 14;
c = 35;
// std::cout << get_a() << std::endl;
std::cout << get_b() << std::endl;
std::cout << get_c() << std::endl;
}
};
struct derive_public : public base {
/* From inside */
auto print() {
// a = 3;
b = 14;
c = 35;
// std::cout << get_a() << std::endl;
std::cout << get_b() << std::endl;
std::cout << get_c() << std::endl;
}
};
int main() {
/* From outside */
derive_private mypriv;
mypriv.print();
// mypriv.a = 2;
// mypriv.b = 5;
// mypriv.c = 29;
// std::cout << mypriv.get_a() << std::endl;
// std::cout << mypriv.get_b() << std::endl;
// std::cout << mypriv.get_c() << std::endl;
derive_protected myprot;
myprot.print();
// myprot.a = 17;
// myprot.b = 8;
// myprot.c = 31;
// std::cout << myprot.get_a() << std::endl;
// std::cout << myprot.get_b() << std::endl;
// std::cout << myprot.get_c() << std::endl;
derive_public mypub;
mypub.print();
// mypub.a = 91;
// mypub.b = 101;
mypub.c = 205;
// std::cout << mypub.get_a() << std::endl;
// std::cout << mypub.get_b() << std::endl;
std::cout << mypub.get_c() << std::endl;
}