Вопросы о static_cast

Я написал фрагмент кода, но меня смущает его вывод:

#include <iostream>

using namespace std;

class B{
public:
    virtual void foo() {cout << "B::foo" << endl;}
};

class D:public B{
public:
    virtual void foo() {cout << "D::foo" << endl;}
    void disp() {cout << "D::disp" << endl;}
};

void func(B *pb){
    D *pd1 = static_cast<D*>(pb);
    pd1->foo();
    pd1->disp();
}

int main(int argc, char *argv[])
{

    B* pb = new B();
    func(pb); 

    return 0;
}

Результат:

B::foo
D::disp

Но насколько мне известно, pb указывает на тип B. И в нем нет функции с именем disp()? Итак, почему он мог получить доступ к disp() функции в классе D?


person injoy    schedule 02.09.2012    source источник
comment
Потому что код лежит на компиляторе. Не делай этого.   -  person Pete Becker    schedule 03.09.2012


Ответы (5)


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

Что вы делаете, так это понижаете значение указателя базового класса до указателя производного класса, даже если он не был инициализирован как таковой. Если disp() попытается получить доступ к членам класса, находящимся в D, но не в B, вы, вероятно, столкнетесь с ошибками сегментирования.

Итог: не используйте static_cast для понижения, если вы не абсолютно уверены, что указатель действительно указывает на экземпляр производного класса. Если вы не уверены, вы можете использовать dynamic_cast, который не работает в случае несоответствия (но есть накладные расходы на RTTI, поэтому избегайте этого, если можете).

dynamic_cast вернет nullptr, если приведение неверное, или выбросит std::bad_cast исключение, если оно приводит ссылки, так что вы будете точно знать, почему это не удается, вместо возможных ошибок повреждения памяти.

person Community    schedule 02.09.2012
comment
никаких негативных эффектов - да, вроде. Поведение не определено, поэтому результат может быть любым. Размышления о том, почему это сработало, просто приводят к ненадежным правилам; в аналогичных случаях, включая повторный запуск того же исполняемого файла, это может иметь ужасные негативные последствия. Как сказал @Tibor: не делайте этого; если нужно, то используйте dynamic_cast. - person Pete Becker; 03.09.2012
comment
D* pd = 0; pd->disp(); также будет печатать D::disp - person Oktalist; 03.09.2012

Линия:

D *pd1 = static_cast<D*>(pb);

Будет выполнено приведение независимо от того, является ли указатель источника B* или D*. В вашем случае результатом будет указатель, указывающий на объект неправильного типа. Метод disp будет работать, потому что он не использует никаких данных-членов или виртуальных функций класса D. В более сложном случае это приведет к нестабильному поведению или сбою.

Ваши объекты полиморфны. Вместо этого вы должны использовать dynamic_cast.

person Kirill Kobelev    schedule 02.09.2012

Я считаю, что в этом контексте важно то, что функция-член disp() не спрятана внутри всех объектов типа D. Это единственная функция, которая существует в одном месте. А попытается ли какой-либо объект вызвать вызов disp(), решает код.

Ваш static_cast создаст то, что компилятор считает указателем на D , независимо от того, какой указатель вы ему передаете. И как только у вас будет указатель на D, компилятор позволит вам попытаться вызвать disp().

Иными словами, static_cast не защитит вас от неправильного приведения указателя.

person Drew Dormann    schedule 02.09.2012

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

5.2.9 Статическая трансляция

Если rvalue типа «указатель на cv1 B» указывает на B, который на самом деле является подобъектом объекта типа D, результирующий указатель указывает на включающий объект типа D. В противном случае результат приведение не определено.

Вы вызвали неопределенное поведение, сделав это static_cast. Компилятор и среда выполнения могут делать что угодно и по-прежнему соответствовать требованиям, когда программа вызывает неопределенное поведение.

person David Hammen    schedule 02.09.2012

Вам необходимо понимать разницу между различными типами приведений C ++.

Здесь вы используете статическое приведение, что равносильно тому, чтобы сказать: «Мне все равно, действительно ли это такого типа или нет - просто постарайтесь изо всех сил».

В этом случае вы хотите знать, действительно ли ваш указатель относится к производному типу, к которому вы его приводите. Вы должны использовать dynamic_cast. Это приведение будет успешным, только если указатель имеет правильный тип. Это означает, что в случае сбоя он вернет указатель NULL.

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

person Carl    schedule 02.09.2012