dynamic_cast "этого" внутри конструктора - PullRequest
6 голосов
/ 10 июня 2011

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

Заголовок:

class A  
{  
public:  
    virtual                ~A() {}
    void                    printA();
};

class B
{
public:
                            B();
    virtual                ~B() {}
    void                    printB();

private:
    std::string             message_;
};

class C : public A, public B
{
public:
                        C() {}
    virtual                ~C() {}
};

Источник:

void A::printA() { cout << "A" << endl; }
B::B()
{
    A* a = dynamic_cast< A* >( this );
    if ( a ) {
        message_ = std::string( "A and B" );
    } else {
        message_ = std::string( "B" );
    }
}
void B::printB() { cout << message_.c_str() << endl; }

Main:

int main( int argc, char* argv[] )
{
    cout << "Printing C..." << endl;
    C c;
    c.printA();
    c.printB();

    cout << "Checking again..." << endl;
    cout << !!dynamic_cast< A* >( &c ) << endl;

    return EXIT_SUCCESS;
}

Результат:

Printing C...
A
B
Checking again...
1

Итак, dynamic_cast работает для множественного наследования (никаких сюрпризов!), Но почему бы не вызвать его во время выполнения для указателя this в B :: B ()? Я думал, что объект полностью сформировался один раз внутри тела конструктора, т. Е. Вся память была выделена для компонентных объектов, они еще не были инициализированы. Я понимаю, что это зависит от порядка конструктора суперкласса, но в этом примере A вызывается раньше B.

Я явно не понимаю, что именно происходит под капотом, может кто-нибудь, пожалуйста, просветите меня?

Спасибо, Кэм Бамбер.

Ответы [ 5 ]

7 голосов
/ 10 июня 2011

Обычно стандарт говорит, что он не будет работать (dynamic_cast) во время создания объекта.

Редактировать: Добавлено на основе комментария VJo ниже.

Примечание: приведение от 'B' к 'A' с использованием динамического приведения должно работать, потому что мы приводим объект извведите «C».Если мы добавим следующий код в main:

B  bObj;
B& bRef = c;
B* bPtr = &c;
std::cout << !!dynamic_cast<A*>(&bObj) << std::endl;
std::cout << !!dynamic_cast<A*>(&bRef) << std::endl;
std::cout << !!dynamic_cast<A*>( bPtr) << std::endl;

Дополнительный вывод будет:

0   // Can not convert a B to an A
1   // Can convert this B to an A because it is really a C.
1   // This is  what we are reeling doing in B::B() that fails
    // It is not the dynamic_cast<> that fails but the conversion of this from C* to B*
    // That is causing UB

В конструкторе произойдет сбой, поскольку объект сформирован не полностью.Используя это, мы пытаемся преобразовать указатель C в указатель B до запуска конструктора C (код, определенный пользователем).Таким образом, использование this в B :: B () в качестве указателя на объект C завершается неудачно, поэтому при вызове dynamic_cast <> он не может выполнить то, что вам нужно, из-за UB.

12.7 Построение и уничтожение [class.cdtor]

Пункт 3

Для явного или неявного преобразования указателя (glvalue), ссылающегося на объект класса X, в указатель (ссылка) к прямому или косвенному базовому классу B X, должно начаться строительство X и построение всех его прямых или косвенных оснований, которые прямо или косвенно являются производными от B, и уничтожение этих классов не должно быть завершено, в противном случаеконверсия приводит к неопределенному поведению.Чтобы сформировать указатель на (или получить доступ к значению) прямого нестатического члена объекта obj, должно начаться построение obj, а его уничтожение не должно быть завершено, в противном случае вычисление значения указателя (или доступ к члену)значение) приводит к неопределенному поведению.

[Пример:

struct A { };
struct B : virtual A { };
struct C : B { };
struct D : virtual A { D(A*); };
struct X { X(A*); };
struct E : C, D, X 
{ 
    E() : D(this),  // undefined: upcast from E* to A*
                    // might use path E* → D* → A* 
                    // but D is not constructed 
                    // D((C*)this), 
                    // defined: 
                    // E* → C* defined because E() has started 
                    // and C* → A* defined because
                    // C fully constructed 
      X(this) { // defined: upon construction of X,
                    // C/B/D/A sublattice is fully constructed
      } 
};

- конец примера]

3 голосов
/ 10 июня 2011

Каждый конструктор базового класса выполняется перед конструктором производного класса, а во время конструктора B динамический тип объекта равен B;он не становится C, пока вы не введете конструктор C.Таким образом, вы не можете делать ничего, что требует динамического типа C: вы не можете выполнять перекрестное приведение к любому из C других базовых классов, и если вы вызываете виртуальную функцию, вы не получите никаких переопределений, предоставляемыхC.

Под капотом динамический тип (по крайней мере, в большинстве реализаций) определяется указателем на объекте (известным как «vptr»), который указывает на некоторые статические данные, указывающие свойствакласс, включая таблицу виртуальных функций (известную как «vtable») и информацию, необходимую для dynamic_cast и typeid.Перед каждым конструктором это обновляется, чтобы указывать на информацию для класса, находящегося в стадии разработки.

2 голосов
/ 10 июня 2011

Поскольку B не наследуется от A (B - самый родительский класс), динамический тип B во время его конструктора равен B.Только когда построены оба родителя A и B, ребенок C может быть сконструирован с учетом бокового dynamic_cast ing.

2 голосов
/ 10 июня 2011

Во время построения A динамический тип равен A независимо. Это потому, что вы начнете вызывать функции-члены производных классов и получать доступ к производным переменным-членам до того, как они будут созданы, что будет UB и очень плохо.

0 голосов
/ 10 июня 2011

Это не работает внутри B, потому что B не наследует от A

...