Как работает этот код для запрета наследования?

Я нашел довольно странный код:

class Base {
public:
    virtual bool IsDerived() const { return false; }
};

class Derived : public Base {
public:
    bool IsDerived() const { return true; }
};

Derived* CastToDerived( Base* base )
{
    // private and protected inheritance from Derived is prohibited
    Derived* derived = dynamic_cast<Derived*>(base);
    if( derived == 0 ) {
       assert( !base->IsDerived() );
    }
    return derived;
}

Я не понимаю отрывок о частном и защищенном наследовании.

Предположим, я наследую от Derived с модификатором protected:

class FurtherDerived : protected Derived {
};

Что случается? Как это assert сработает?


person sharptooth    schedule 26.08.2011    source источник
comment
Пожалуйста, объясните, что я наследую от Derived с защищенным модификатором. Вы говорите, что получаете новый производный класс от «производного» класса или защищенного наследования «базового» класса?   -  person Ajeet Ganga    schedule 26.08.2011
comment
Где вы нашли этот код? Это не имеет никакого смысла. Если Derived публично не наследуется от Base, единственное, что может быть передано CastToDerived(), — это указатель Base, потому что не будет никакого способа преобразовать указатель Derived в указатель Base. Таким образом, утверждение никогда не может быть вызвано.   -  person Praetorian    schedule 26.08.2011
comment
@Praetorian есть способы получить указатель Base из Derived, даже если Base является частным предком. Например, добавив Base* Derived::getBase() { return this; }   -  person hamstergene    schedule 26.08.2011
comment
@Eugene Homyakov Хорошо, хороший пример, я не думал о явной функции преобразования.   -  person Praetorian    schedule 26.08.2011


Ответы (4)


Если у вас есть наследство Protected или Private, вы не можете:

Base *ptr = new Derived();

ни ты не можешь сделать,

Derived *ptr1 = new Derived();
Base *ptr = ptr1;

Это потому, что Base является недоступной базой Derived.

Поскольку указатель класса Base не может указывать на объект класса Derived, эта проверка выглядит излишней.


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

Короче говоря, указатель класса Base может указывать на объект класса Derived, даже если производным является protected или private.

Учитывая вышеизложенное,

Согласно стандарту C++:
5.2.7.8:

Проверка во время выполнения логически выполняется следующим образом:
— Если в самом производном объекте, на который указывает (ссылается) v, v указывает (ссылается) на общедоступный подобъект базового класса объекта T, и если только один объект типа T является производным от подобъекта, на который указывает (на который ссылается) v, результатом является указатель (ссылающееся на lvalue) на этот объект T.
В противном случае, если v указывает (относится) к общедоступному подобъекту базового класса наиболее производного объекта, а тип наиболее производного объекта имеет базовый класс типа T, который является однозначным и public, результатом является указатель (отсылка к lvalue) к подобъекту T самого производного объекта.
— в противном случае проверка во время выполнения завершится ошибкой.

Обратите внимание, что стандарт специально требует, чтобы вывод был общедоступным
. Таким образом, dynamic_cast обнаружит обработку приведения как неправильное приведение, если вывод protected или private, и вернет NULL (поскольку вы используете указатель), и будет вызываться assert.

Так что да, код вполне действителен. И это действительно делает то, что говорится в комментарии


Этот образец демонстрирует, что он работает в соответствии с комментариями:

#include<iostream>
class Base 
{
    public:
        virtual bool IsDerived() const { return false; }
};

class Derived : protected Base 
{
    public:
        bool IsDerived() const { return true; }
        Base* getBase() { return this; }
};

Derived* CastToDerived( Base* base )
{
     // private and protected inheritance from Derived is prohibited
     Derived* derived = dynamic_cast<Derived*>(base);
     if( derived == 0 ) 
     {
         std::cout<< "!base->IsDerived()";
     }
     return derived;
}


int main()
{
    Derived *ptr3 = new Derived();
    Base *ptr = ptr3->getBase();
    Derived *ptr2 = CastToDerived(ptr);
    return 0;
}
person Alok Save    schedule 26.08.2011
comment
Вы неправильно прочитали вопрос. Derived имеет публичное наследование от Base . Это данность. Вопрос касается некоторого класса NonPublicDerived, который имеет частное или защищенное наследование от класса Derived. - person David Hammen; 26.08.2011
comment
@David Hammen: я не понимаю, какой класс NonPublicDerived вы здесь имеете в виду. - person Alok Save; 27.08.2011
comment
Класс, о котором я говорю, — это некий неопределенный класс, производный от класса Derived. Вот вопрос, на который ОП попросил нас ответить: Предположим, я наследую от Derived с защищенным модификатором. Что происходит?. Там ничего о том, как формируется класс Derived. То, как формируется класс Derived, является данностью: публичное наследование от класса Base. Вопрос о каком-то неуказанном классе, который наследуется от Derived, но делает это с наследованием либо protected, либо private. - person David Hammen; 27.08.2011
comment
@David Hammen: Я не уверен, что понимаю, что вы хотите сказать. Когда я прочитал вопрос, ОП четко спрашивает здесь, может ли этот dynamic_cast делать то, что говорит комментарий, то есть утверждать, было ли происхождение частным или защищенным. И ответ Да, это так. И причина в том, что в стандарте специально используется слово public при определении dynamic_cast, как указано в ответе. - person Alok Save; 27.08.2011
comment
Вы пробовали это, как просил ОП? Я сделал. Смотрите мой ответ. Вы отвечаете на вопрос, который ОП не задавал. ОП никогда не спрашивал о class Derived : protected Base. - person David Hammen; 27.08.2011
comment
@David Hammen: Пример кода, указанный выше в ответе, не делает того, что говорят комментарии в коде? Я сдаюсь. Слишком поздно для меня и слишком сонный, чтобы попытаться понять, что вы хотите сказать. - person Alok Save; 27.08.2011
comment
Вы не можете сделать: конечно, вы можете, но не в каждом контексте! - person curiousguy; 21.12.2011

IsDerived — это виртуальная функция, определенная в базовом классе, и функции разрешаются на основе статического типа объекта, с помощью которого вы вызываете функцию. Это значит, что это не проблема. Это будет работать. (Или, может быть, я что-то упустил в вашем вопросе).

person Nawaz    schedule 26.08.2011
comment
-1: вызов функции-члена, объявленной виртуальной в каком-либо базовом классе, с помощью указателя базового класса приведет к вызову наиболее производной реализации этого метода. Если бы С++ работал так, как вы утверждаете, огромная гора программ на С++, которые зависят от С++, выполняющего виртуальную диспетчеризацию, никогда бы не работала. - person David Hammen; 26.08.2011
comment
@David: Вы либо неправильно поняли мой пост, либо не знаете, как происходит виртуальная отправка. Все, что я хочу сказать, это: ideone.com/HsTrg. Объясните мне его вывод! - person Nawaz; 26.08.2011

dynamic_cast выполняет проверку во время выполнения (5.2.7/8).

Проверка во время выполнения завершится ошибкой, если Base наследуется как защищенный или закрытый Derived.

Значением неудачной проверки во время выполнения при приведении к указателю является указатель NULL (5.2.7/9).

Таким образом, код представляет собой обходной путь для частных и защищенных потомков: если вы наследуете Derived с protected или private, dynamic_cast вернет NULL и будет выполнена пользовательская проверка.

person hamstergene    schedule 26.08.2011
comment
Хорошо, но как вызывающая сторона CastToDerived может преобразовать указатель Derived в указатель Base, если наследование не является общедоступным? - person Praetorian; 26.08.2011
comment
Смотрите мой другой комментарий в исходном посте :) - person hamstergene; 26.08.2011
comment
Этот вопрос не в том, как Derived наследуется от Base. Это данность. Вопрос в том, предположим, я наследую от Derived с защищенным модификатором. Что случается? - person David Hammen; 27.08.2011
comment
@ Дэвид Я полагал, что в том, что комментарий не означает то, что он говорит, больше смысла, чем в том, что однажды dynamic_cast волшебным образом вернул 0, когда этого не должно было быть. - person hamstergene; 27.08.2011
comment
@Eugene: Именно. Я предполагаю, что автор этого кода пытался решить проблему, предположил неправильную причину и написал неработающее решение для этой неправильной догадки. Позже он решил настоящую проблему, но никогда не удалял свой в основном безобидный хлам. Типичная отладка карго-культа. Я сказал в основном безвредный, потому что это просто бомба, которая только и ждет, чтобы взорваться. Когда-нибудь кто-нибудь напишет класс Foo : public Base, который также переопределит IsDerived(). Применение CastToDerived к указателю Foo приведет к удалению ядра. - person David Hammen; 27.08.2011

Я не понимаю отрывок о частном и защищенном наследовании.

Ответ прост. Этот комментарий, как и многие, многие другие комментарии, является комментарием, который никоим образом не описывает код, форму или форму.

Пример:

class PrivateDerived : private Derived {
public:
   Base* cast_to_base () {
      return dynamic_cast<Base*>(this);
   }   
};  

void check_base (const char * id, Base* pbase) {
   if (pbase == 0) {
      std::cout << id << " conversion yields a null pointer.\n";
   }
   else {
      std::cout << id << "->IsDerived() = "
                << pbase->IsDerived() << "\n";
      Derived* pderived = CastToDerived (pbase);
      std::cout << "CastToDerived yields "
                << (pderived ? "non-null" : "null") << " pointer.\n";
      std::cout << "pderived->IsDerived() = "
                << pderived->IsDerived() << "\n\n";
   }
}

int main () {
   PrivateDerived private_derived;

   // Good old c-style cast can convert anything to anything.
   // Maybe a bit disfunctional, but it works in this case.
   check_base ("c_style_cast", (Base*)&private_derived);

   // The cast_to_base method can see the private inheritance,
   // and does so without invoking undefined behavior.
   check_base ("cast_method", private_derived.cast_to_base());

   return 0;
}

Протестировано с несколькими версиями gcc и clang; ни один из них не поднял это утверждение assert.

Дополнение
Я подозреваю, что произошло то, что на какой-то конкретной машине с каким-то конкретным компилятором рассматриваемый код каким-то образом сумел сделать то, что, по мнению автора, он должен был делать. Я подозреваю, что автор никогда не проверял эту предполагаемую проверку, чтобы убедиться, что она действительно работает так, как рекламируется.

person David Hammen    schedule 26.08.2011
comment
утверждение терпит неудачу, если Derived имеет защищенное или частное наследование от Base. как в вопросе: допустим, я наследую от Derived с защищенным модификатором. - person Matt K; 26.08.2011
comment
@mkb: я протестировал приведенный выше код с несколькими компиляторами. Утверждение не провалилось ни с одним из них. Тем не менее, у меня, очевидно, нет доступа ко всем компиляторам C++. У некоторых это может потерпеть неудачу. Если вышеперечисленное не сработает с одним, мне было бы любопытно. - person David Hammen; 26.08.2011
comment
Я сказал, если Derived имеет защищенное или частное наследование, а не PrivateDerived. - person Matt K; 26.08.2011
comment
Что такого в этом вопросе, что так много ответов полностью ортогональны рассматриваемому вопросу? Вот что спросил ОП: Предположим, я наследую от Derived с защищенным модификатором. Что случается? Как будет срабатывать это утверждение? В этом вопросе ни слова о том, что произойдет, если класс Derived имеет непубличное наследование от Base. - person David Hammen; 26.08.2011