динамическое приведение к не производному объекту для вызова функции работает?

Я читал в книге C ++, что вы можете использовать dynamic_cast для понижения указателя на базовый объект до указателя на производный объект, если объект, на который он указывает, фактически является объектом производного типа. Формат dynamic_cast<Derived*>(basePointer), Кроме того, я прочитал на этом сайте, что dynamic_cast должен возвращать нулевой указатель, если объект, на который он указывает, не может быть преобразован в производный тип класса.

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

#include <iostream>
using namespace std;

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

class Base {
public:
virtual void doSomething () {
cout << "Done something!" << endl;
}
};

class Derived: public Base {
public:
virtual void doSomething () {
cout << "You done goofed!" << endl;
}
void printFoo () {
cout << "Foo" << endl;
}

private:
int x;
};

int main () {
Object o;
Object* p = &o;

if(dynamic_cast<Derived*>(p))
cout << "Yep, baby is derived!" << endl;
else
cout << "Isn't derived." << endl;

dynamic_cast<Derived*>(p)->printFoo();return 0;
}

Здесь Base — это базовый тип класса, предназначенный для полиморфного использования (то есть он содержит виртуальную функцию), а Derived — это тип класса, производный от Base. Объект — это простой класс, который не связан ни с одним из классов, но его деструктор сделан виртуальным, чтобы его можно было использовать полиморфно. Я объясню назначение Derived :: x через минуту. В основной функции создается объект и указатель на объект назначается его адресу. Он проверяет, является ли Derived потомком этого указателя, и печатает, является ли он или нет. Затем он приводит этот указатель к указателю типа Derived, и вызывается функция printFoo Derived.

Это не должно работать, но это работает. Когда я запускаю его, он отображает «Не получено», поэтому он ясно понимает, что Obect не является производным от базы, но затем выводит «Foo» на экран и завершает работу без ошибок. Однако, если я добавлю строку x = 1; к функции printFoo она завершается с ошибкой сегментации, так как пытается присвоить переменную, которой нет в этом объекте. Кроме того, кажется, что это работает только с не виртуальными функциями. Например, если я пытаюсь вызвать виртуальную функцию doSomething, я получаю ошибку Сегментации до того, как «Вы сделали глупость!» когда-либо распечатывается.

Что тут происходит? не должны dynamic_cast<Derived*>(p) вернуть нулевой указатель, чтобы попытка вызвать printFoo из этого автоматически вызывала ошибку? Почему это работает, а не должно?

3

Решение

Вы уже установили, что dynamic_cast возвращает указатель NULL, поэтому реальный вопрос заключается в том, почему вызовы функций для указателя объекта NULL в некоторых случаях работают, а в других — нет.

В ВСЕ из этих случаев вы получаете неопределенное поведение. Просто помните, что undefined не всегда означает сбой — иногда вы получаете совершенно разумные результаты. Вы просто не можете положиться на это.

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

printFoo не является виртуальным, поэтому нет необходимости использовать vtable для доступа к нему. doSomething виртуально так делает нужен Vtable. Нулевой указатель не имеет vtable, поэтому вызов doSomething взрывается сразу.

printFoo не использует ни одного из членов объекта, пока вы не добавите x = 1 линия к этому. Пока компилятор не генерирует код, который обращается к this указатель, скорее всего, будет работать нормально.

1

Другие решения

Это работает потому что

dynamic_cast<Derived*>(p)->printFoo();

возвращает null указатель, как и ожидалось, но null указатель имеет тип Производный. Когда это вызывает printFooработает потому что printFoo не использует переменную-член, в этом случае он будет ссылаться на this для использования этой переменной, и вылетает из-за разыменования null указатель. Поскольку переменная-член не используется, нет ссылки на this, нет проблем (вот почему он дает Segfault, когда вы используете x в методе).

Это так же, как следующий простой код:

class A{
public:
int x;
void f1(){}
void f2(){x=1;}
}

A* x=nulptr;
x->f1(); // will work
x->f2(); // UB
2

Вы получаете доступ к null указатель без использования какого-либо фактического состояния объекта, что является безопасным, но бессмысленным.

Если вы измените Derived :: printFoo () на следующее, вы фактически окажетесь на «неопределенной территории поведения» и, скорее всего, будете иметь segfault:

void printFoo () {
cout << "Foo: " << x << endl;
}

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

dynamic_cast<Derived &>(*p).printFoo(); // kaboom! throws std::bad_cast
1
По вопросам рекламы [email protected]