Вопрос по работе с dynamic_cast
Проблема в следующем: непонятна работа нижеприведенного кода. Назначение шаблона ptr_cast - проверить, действительно ли ptr указывает на класс типа To и если да, то вернуть указатель этого типа на класс, если нет, то выкинуть bad_cast.
{
try
{
To result = dynamic_cast< To >((To)ptr);
if(result)
return result;
}
catch(...)
{
throw(bad_cast());
}
}
class A
{
public:
A(){};
virtual void a()
{std::cout<<"A"<<std::endl;};
};
class BB:public A
{
int b;
public:
BB():b(12){};
virtual void a(){std::cout<<"BB"<<std::endl;};
void bb(){std::cout<<b<<std::endl;};
};
class C:public BB
{
};
void Ex1()
{
BB * p = 0;
A a;
a.a();
p = ptr_cast< BB * >(&a);
p->a();
p->bb();
}
Но после выполнения функции Ex1() в консоли следующий результат:
A
-858993460
Как так получается, Ведь у класса А нет метода bb()?
{
try
{
To result = dynamic_cast< To >((To)ptr);
if(result)
return result;
}
catch(...)
{
throw(bad_cast());
}
}
Взгляни на свой код.
Постарайся НИКОГДА не использовать преобразования С++ стиля совместно с С-стилем и никогда не используй void* !
Перед тем, как сделать dynamic_cast, ты делаешь reinterpret_cast в С-стиле:
To result = dynamic_cast< To >((To)ptr);
Сделай так:
To ptr_cast(From* ptr)
{
try
{
To result = dynamic_cast<To>(ptr);
if(result)
return result;
}
catch(...)
{
throw( bad_cast() );
}
}
А warning C4541 ты не получал?
нет, тоько этот
нет, тоько этот
Ок, RTTI включена, значит, причину см выше.
Смущает, правда вот это:
"The value of a failed cast to pointer type is the null pointer. A failed cast to reference type throws a bad_cast exception."
Т.е. т.к. ты работаешь с указателями, try не сработает, т.е.:
To ptr_cast(From* ptr)
{
To result = dynamic_cast<To>(ptr);
if(result)
return result;
throw( bad_cast() );
}
Взгляни на свой код.
Постарайся НИКОГДА не использовать преобразования С++ стиля совместно с С-стилем и никогда не используй void* !
Перед тем, как сделать dynamic_cast, ты делаешь reinterpret_cast в С-стиле:
To result = dynamic_cast< To >((To)ptr);
Сделай так:
To ptr_cast(From* ptr)
{
try
{
To result = dynamic_cast<To>(ptr);
if(result)
return result;
}
catch(...)
{
throw( bad_cast() );
}
}
Я выполняю упражнение из Страуструпа: написать шаблон, аналогичный dynamic_cast, только в случае ошибки он должен не возвращать 0, а выкидывать исключение. В первый раз я так и написал, как вы сказали! А дальше начал придумывать пути, чтобы убрать From, но как, не придумал.
разве dynamic_cast не должен проверить, принадлежит ли объект, находящийся по этому адресу нужному мне типу, а потом вернуть 0 или указатель на этот объект?
я понимаю, почему работает вот эта сточка:
разве dynamic_cast не должен проверить, принадлежит ли объект, находящийся по этому адресу нужному мне типу, а потом вернуть 0 или указатель на этот объект?
ну это в стандарте надо смотреть, должен, или не должен (к сожалению, нет под рукой)...
ведь здесь: (To)ptr - ты жестко приказываешь компилятору считать, что ptr указывает на объект типа To (и никаких гвоздей - "C-style cast" - почти тоже самое, что reinterpret_cast), затем вызываешь dynamic_cast c целью преобразовать указатель на объект типа To в указатель на объект
того же типа - будут ли проверки? - не очевидно,
так или иначе, reinterpret_cast у тебя и срабатывает (хотя у меня возвращает ноль - по видимому, другой компилятор).
я понимаю, почему работает вот эта сточка:
ничего-себе "срабатывает"...
нестатическим функциям классов, как известно, кроме явных аргументов передается указатель "this" на объект класса, смотри:
0040183C mov edx,dword ptr [ebp-4] ;в edx адрес a
0040183F mov eax,dword ptr [edx] ;в eax адрес vftable, то бишь первой функции оттуда и ,т.к. и у A, и у BB это a(), на неё и попадаем
00401841 mov esi,esp
00401843 mov ecx,dword ptr [ebp-4] ;в ecx "this"
00401846 call dword ptr [eax]
00401848 cmp esi,esp
0040184A call __chkesp (00420d50)
60: p->bb();
0040184F mov ecx,dword ptr [ebp-4] ;в ecx "this"
00401852 call @ILT+230(BB::bb) (004010eb)ж т.к. bb() не является виртуальной, компилятор жестко подствил её адрес (в случ. виртуальной, так же, как и выше вычислили бы адрес и получили бы access violation (в лучшем случае))
при этом bb() пытется обратиться к data member "b", точнее, к тому месту, где он должен быть:
0040199D push offset @ILT+195(std::endl) (004010c8)
004019A2 mov eax,dword ptr [ebp-4]
004019A5 mov ecx,dword ptr [eax+4]
004019A8 push ecx ; если бы p указывал на BB, в ecx было бы 12
004019A9 mov ecx,offset std::cout (00478898)
и, разумеется, выдает бессмымсленный результат...
Ваще, это долгий разговор, тебе, наверное, сюда:
http://safari.oreilly.com/JVXSL.asp?x=1&mode=section&sortKey=rank&sortOrder=desc&view=book&xmlid=0-201-83454-5&g=&srchText=Lippman&code=&h=&m=&l=1&catid=&s=1&b=1&f=1&t=1&c=1&u=1&r=&o=1&n=1&d=1&p=1&a=0&page=1
и, разумеется, выдает бессмымсленный результат...
так код функции bb() можно заменить на
так код функции bb() можно заменить на
Ну если не обращаться к полям объекта, то проблем не будет.
Ну если не обращаться к полям объекта, то проблем не будет.
вот этого я и не понимаю. Я же создал объект класса А, почему у него имеется метод класса ВВ?
вот этого я и не понимаю. Я же создал объект класса А, почему у него имеется метод класса ВВ?
Потому что классы и объекты существуют только "на уровне исходного кода" (т.е. C++), в машинных кодах (почему я и показал тебе дизассемблер) все "глобальное". У тебя p - указатель на объект типа BB - компилятор и обращается с ним как с таковым, не заботясь о том, что у тебя там на самом деле.
Вот еще примерчик:
{
public:
A(){};
virtual void c()
{std::cout<<"C"<<std::endl;};
};
class BB
{
int b;
public:
BB():b(12){};
virtual void a(){std::cout<<"BB"<<std::endl;};
void bb(){std::cout<<b<<std::endl;};
};
void Ex1()
{
A a;
BB * p=(BB*)&a;
p->a();
p->bb();
}
тоже вроде-бы "срабатывает", но ...
Потому что классы и объекты существуют только "на уровне исходного кода" (т.е. C++), в машинных кодах (почему я и показал тебе дизассемблер) все "глобальное". У тебя p - указатель на объект типа BB - компилятор и обращается с ним как с таковым, не заботясь о том, что у тебя там на самом деле.
Вот еще примерчик:
{
public:
A(){};
virtual void c()
{std::cout<<"C"<<std::endl;};
};
class BB
{
int b;
public:
BB():b(12){};
virtual void a(){std::cout<<"BB"<<std::endl;};
void bb(){std::cout<<b<<std::endl;};
};
void Ex1()
{
A a;
BB * p=(BB*)&a;
p->a();
p->bb();
}
тоже вроде-бы "срабатывает", но ...
я понимаю, что в машинном коде нет понятия "класс". Если я говорю компилятору "создай объект класса А и сохрани указатель на него в р", то компилятор так и должен сделать. Если я ему говорю "не делай проверки типов указателей, а просто присвой указателю на класс BB указатель на класс А", то он это и должен сделать. Кто компилятору говорит "положи еще на всякий случай функцию bb()"? По идее программа должна на этапе выполнения либо вывалится с какой-нить ошибкой, либо выдать какой-нибудь мусор, т.к. в предполагаемом месте функции нет. Но почему-то ни того, ни другого не происходит.
я понимаю, что в машинном коде нет понятия "класс". Если я говорю компилятору "создай объект класса А и сохрани указатель на него в р", то компилятор так и должен сделать. Если я ему говорю "не делай проверки типов указателей, а просто присвой указателю на класс BB указатель на класс А", то он это и должен сделать. Кто компилятору говорит "положи еще на всякий случай функцию bb()"? По идее программа должна на этапе выполнения либо вывалится с какой-нить ошибкой, либо выдать какой-нибудь мусор, т.к. в предполагаемом месте функции нет. Но почему-то ни того, ни другого не происходит.
Не "указатель на класс", а "указатель на объект класса" (я не придираюсь к словам, в данном случае это существенно).
Объект этот можно представить в виде ассемблерной или C структуры, состоящей из адреса vftable (DWORD, если есть виртуальные ф-ции) и data members.
Например sizeof(BB)=(4 byte: адрес vftable)+ (4 byte: int b)=8;
sizeof(A)=4=(4 byte: адрес vftable); И когда ты создаешь объект,
ты выделяешь память под такую структуру. Member functions же не тиражируются, они как-бы "глобальные", и каждая из них в единственном экземпляре для всех объектов, их не надо "класть на всякий случай".
Когда ты говоришь компилятору, что p указывает на объект типа BB (т.е. пишешь BB* p), то имеешь полное право вызвать BB::b() через этот указатель (передав этой ф-ции (через стек или регистры) значение p).
"Вывылиться" программа могла бы в случае, если бы BB:b() обращалась бы к data members или была бы виртуальной (здесь результат непредсказуемый, и, собственно, для уменьшения, по возможности, этой непредсказуемости и придуман C++ casting).
Заметь, что p->a() у тебя "срабатывает" только в кавычках: компилятор пытается вызвать BB::a() через vftable от A, почему и попадает на A::a() (в моем примере попадаем на A::c()).
Кстати, разьве Страуструпп этого не объясняет?
Слишком уж фундаментальные вопросы, чтобы узнавать от кого попало (:)) - смотри литературу.
Не "указатель на класс", а "указатель на объект класса" (я не придираюсь к словам, в данном случае это существенно).
Объект этот можно представить в виде ассемблерной или C структуры, состоящей из адреса vftable (DWORD, если есть виртуальные ф-ции) и data members.
Например sizeof(BB)=(4 byte: адрес vftable)+ (4 byte: int b)=8;
sizeof(A)=4=(4 byte: адрес vftable); И когда ты создаешь объект,
ты выделяешь память под такую структуру. Member functions же не тиражируются, они как-бы "глобальные", и каждая из них в единственном экземпляре для всех объектов, их не надо "класть на всякий случай".
Когда ты говоришь компилятору, что p указывает на объект типа BB (т.е. пишешь BB* p), то имеешь полное право вызвать BB::b() через этот указатель (передав этой ф-ции (через стек или регистры) значение p).
"Вывылиться" программа могла бы в случае, если бы BB:b() обращалась бы к data members или была бы виртуальной (здесь результат непредсказуемый, и, собственно, для уменьшения, по возможности, этой непредсказуемости и придуман C++ casting).
Заметь, что p->a() у тебя "срабатывает" только в кавычках: компилятор пытается вызвать BB::a() через vftable от A, почему и попадает на A::a() (в моем примере попадаем на A::c()).
Кстати, разьве Страуструпп этого не объясняет?
Слишком уж фундаментальные вопросы, чтобы узнавать от кого попало (:)) - смотри литературу.
Спасибо, поищу у Страуструпа