Публичное и частное наследование в C++

Как известно из литературы для публичного наследования, объект дочернего класса (подкласса) также может рассматриваться как объект базового класса (суперкласса). Почему объект подкласса нельзя рассматривать как объект суперкласса, когда наследование защищено или является частным?

7 ответов

Решение

Потому что вы не можете видеть это:

class Base
{
    public: virtual ~Base() {}
};

class PublicDerived: public Base
{    };

class PrivateDerived: private Base
{    };

int main()
{
    PublicDerived   publicD;
    PrivateDerived  privateD;

    Base&           base1 = publicD;
    Base&           base2 = privateD; // ERROR
} 

Таким образом, вы не можете использовать объект PrivateDerived, где может использоваться объект Base.
Так что он никогда не будет действовать как объект базового класса.

В общем, вы найдете в литературе (и в других ответах здесь), что protected / private наследование подразумевает, что класс не может быть использован как base, Факт (некоторые другие ответы на это указывают) заключается в том, что операция влияет только на видимость наследования. derived класс base класс, даже если внешний код не может его увидеть.

любой friend или класс сможет использовать эти отношения:

struct base {
   virtual void foo() { std::cout << "base" << std::endl; }
};
void take_base( base& b ) {}
class derived : base // private {
   friend base& to_base( derived& );
   virtual void foo() { std::cout << "derived" << std::endl; }
public:
   base & as_base() { return *this; }
   void call_function() { take_base(*this); } // ok: inside derived, it knows that it is
                                              // in fact a base
};
base& to_base( derived& d ) {
   return d;
}
int main() {
   derived d;
   //d.foo();      // error
   //take_base(d); // error
   take_base( d.as_base() ); // ok, the conversion is performed internally where
                             // access is granted: print "derived"
   take_base( to_base(d) );  // ok, the conversion is performed in a friend function
                             // that has access: print "derived"
}

Теперь, хотя технически это так, семантически, когда вы используете private наследство вы пытаетесь смоделировать не is-a а скорее implemented-in-terms-of отношения. Это важная часть: при чтении кода, если вы видите private наследование, о котором вы не должны думать, а реализовано в терминах.

public наследование служит целям отношений. То есть:

class A {};
class B : public A {};

Class B is a version of class A.

private Наследование служит цели отношения. Вы можете написать практически любой класс, используя приватное наследование, используя модель контейнера:

class A {};
class B : private A {};

может быть переписан (и чаще всего, должен быть переписан для ясности):

class A {};
class B
{
private:
    A a;
};

protected наследование похоже на private, но в действительности почти никогда не должны использоваться (Скотт Мейерс и Херб Саттер оба приводят причины для этого в своих соответствующих книгах).

"Почему" просто при рассмотрении того, как работает механизм: потому что защищенное и частное наследование предназначено для работы таким образом.

Это, вероятно, не достаточно, чтобы ответить на намерение вопроса, хотя. Вы можете спросить "а зачем иметь частное и защищенное наследование, если вы не можете использовать результирующие объекты как экземпляры базового класса?"

Что ж, непубличное наследование предназначено для облегчения отношений "реализовано в терминах" между двумя классами (тогда как публичное наследование облегчает отношения "есть"). Другими словами, вы намереваетесь повторно использовать часть или все функциональные возможности базового класса для предоставления услуг своим потребителям.

Этот сценарий почти всегда лучше реализуется путем агрегации, а не наследования (т. Е. Наличия объекта-члена "базового" класса), и я бы сказал, что частное наследование - это нечто лучшее, оставленное в покое.

Взгляните на это для более длинной рецензии, которая расширяет вышеупомянутое.

Обновление: как указано в комментариях ниже, есть некоторые (по общему признанию, редкие) случаи, когда непубличное наследование обеспечивает механизм для архитектурной функциональности, который иначе был бы невозможен. Прочитайте их, поскольку изучение краев языка может быть весьма поучительным. Но попробуйте сделать это как можно меньше.

Вкратце, потому что частное наследование - это наследование реализации, а не интерфейса. Частный подкласс Derived объект не является Base, но реализуется с точки зрения Base, Общественность и защищенные члены Base видны для Derived, но они становятся частными, поэтому недоступны для внешнего мира. Таким образом, частное наследование можно рассматривать как особую форму композиции, которая фактически редко требуется на практике. (И защищенного наследования практически никогда не бывает - на самом деле, вероятно, даже Бьярне Страуструп не знает, что означает защищенное наследование.)

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

Это, безусловно, можно считать объектом суперкласса. Однако такое рассмотрение ограничено (модификатором открытого / защищенного / частного наследования), но только само по себе (частное наследование) или его подклассами (защищенное наследование).

Всем внешним объектам не разрешается рассматривать класс как таковой, подобно тому, как они не имеют доступа к защищенным или закрытым методам или переменным. Аналогия довольно уместна, если выражена правильно.

Таким образом, сам класс, его подклассы (и друзья) могут воспринимать это как отношения "как есть", но внешнему миру это запрещено.

Следующий код показывает это в действии:

class Base {
    public: virtual ~Base() {}
};

class PublicDerived: public Base
{ };

class ProtectedDerived: protected Base {
    void test() {
        Base* base2 = this; // OK
    }
};

class ProtectedSubClass: public ProtectedDerived {
    void test() {
        Base* base2 = this; // OK
    }
};

class PrivateDerived: private Base {
    void test() {
        Base* base2 = this; // OK
    }
};

class PrivateSubClass: public PrivateDerived {
    void test() {
        Base* base2 = this; // Error (line 28)
    }
};

int main()
{
    PublicDerived   publicD;
    ProtectedDerived protectedD;
    PrivateDerived  privateD;

    Base* base1 = &publicD;
    Base* base2 = &protectedD; // Error (line 39)
    Base* base3 = &privateD; // Error (line 40)
} 

Обратите внимание, что не имеет значения, как классы xxxSubClass наследуются от своих суперклассов. Все дело в том, как суперклассы наследуются от Base, что и должно быть.

Компилятор жалуется соответственно:

inherit.cpp(28) : error C2247: 'Base' not accessible because 'PrivateDerived' uses 'private' to inherit from 'Base'
        inherit.cpp(1) : see declaration of 'Base'
        inherit.cpp(20) : see declaration of 'PrivateDerived'
        inherit.cpp(1) : see declaration of 'Base'
inherit.cpp(29) : error C2243: 'type cast' : conversion from 'PrivateSubClass *const ' to 'Base *' exists, but is inaccessible
inherit.cpp(39) : error C2243: 'type cast' : conversion from 'ProtectedDerived *' to 'Base *' exists, but is inaccessible
inherit.cpp(40) : error C2243: 'type cast' : conversion from 'PrivateDerived *' to 'Base *' exists, but is inaccessible

Вы можете думать о публичном / защищенном / частном наследовании как о доступности для любого ученика: это вопрос "сколько вы хотите показать".

Частное (или защищенное, несколько иным способом) наследование - это отношения, которые не проявляются во внешнем мире. Таким образом, вы не можете рассматривать объект производного типа как его частную базу, потому что вы не можете "увидеть", что эта связь даже существует.

Другие вопросы по тегам