Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Вопрос по работе с dynamic_cast

1.9K
12 июля 2005 года
solovey
113 / / 25.07.2004
Доброго времени суток! Помогите пожалуйста разобраться с тонкостями работы субжа.

Проблема в следующем: непонятна работа нижеприведенного кода. Назначение шаблона ptr_cast - проверить, действительно ли ptr указывает на класс типа To и если да, то вернуть указатель этого типа на класс, если нет, то выкинуть bad_cast.

Код:
template<class To > To ptr_cast(void * ptr)
{
    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
A
-858993460

Как так получается, Ведь у класса А нет метода bb()?
430
12 июля 2005 года
craftyfox
157 / / 20.02.2000
А warning C4541 ты не получал?
3
12 июля 2005 года
Green
4.8K / / 20.01.2000
Цитата:
Originally posted by solovey
Код:
template<class To > To ptr_cast(void * ptr)
{
    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);

Сделай так:

Код:
template<class To, class From>
To ptr_cast(From* ptr)
{
  try
  {
    To result = dynamic_cast<To>(ptr);
    if(result)
      return result;
  }
  catch(...)
  {
    throw( bad_cast() );
  }
}
1.9K
12 июля 2005 года
solovey
113 / / 25.07.2004
Цитата:
Originally posted by craftyfox
А warning C4541 ты не получал?


нет, тоько этот

Цитата:
warning C4715: 'ptr_cast<BB *>' : not all control paths return a value

430
12 июля 2005 года
craftyfox
157 / / 20.02.2000
Цитата:
Originally posted by solovey
нет, тоько этот


Ок, 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 не сработает, т.е.:

 
Код:
template<class To, class From>
To ptr_cast(From* ptr)
{
    To result = dynamic_cast<To>(ptr);
    if(result)
      return result;
    throw( bad_cast() );
}
1.9K
12 июля 2005 года
solovey
113 / / 25.07.2004
Цитата:
Originally posted by Green
Взгляни на свой код.
Постарайся НИКОГДА не использовать преобразования С++ стиля совместно с С-стилем и никогда не используй void* !

Перед тем, как сделать dynamic_cast, ты делаешь reinterpret_cast в С-стиле:
To result = dynamic_cast< To >((To)ptr);

Сделай так:
Код:
template<class To, class From>
To ptr_cast(From* ptr)
{
  try
  {
    To result = dynamic_cast<To>(ptr);
    if(result)
      return result;
  }
  catch(...)
  {
    throw( bad_cast() );
  }
}


Я выполняю упражнение из Страуструпа: написать шаблон, аналогичный dynamic_cast, только в случае ошибки он должен не возвращать 0, а выкидывать исключение. В первый раз я так и написал, как вы сказали! А дальше начал придумывать пути, чтобы убрать From, но как, не придумал.

Цитата:
To result = dynamic_cast< To >((To)ptr);

разве dynamic_cast не должен проверить, принадлежит ли объект, находящийся по этому адресу нужному мне типу, а потом вернуть 0 или указатель на этот объект?
я понимаю, почему работает вот эта сточка:

 
Код:
p->a();
Но почему срабатывает вот эта?
 
Код:
p->bb();
430
14 июля 2005 года
craftyfox
157 / / 20.02.2000
Цитата:
Originally posted by solovey

разве dynamic_cast не должен проверить, принадлежит ли объект, находящийся по этому адресу нужному мне типу, а потом вернуть 0 или указатель на этот объект?


ну это в стандарте надо смотреть, должен, или не должен (к сожалению, нет под рукой)...
ведь здесь: (To)ptr - ты жестко приказываешь компилятору считать, что ptr указывает на объект типа To (и никаких гвоздей - "C-style cast" - почти тоже самое, что reinterpret_cast), затем вызываешь dynamic_cast c целью преобразовать указатель на объект типа To в указатель на объект
того же типа - будут ли проверки? - не очевидно,
так или иначе, reinterpret_cast у тебя и срабатывает (хотя у меня возвращает ноль - по видимому, другой компилятор).

Цитата:
Originally posted by solovey

я понимаю, почему работает вот эта сточка:
 
Код:
p->a();
Но почему срабатывает вот эта?
 
Код:
p->bb();


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

Код:
59:       p->a();
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", точнее, к тому месту, где он должен быть:
 
Код:
0040199A   mov         dword ptr [ebp-4],ecx
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
1.9K
14 июля 2005 года
solovey
113 / / 25.07.2004
Цитата:
Originally posted by craftyfox
и, разумеется, выдает бессмымсленный результат...

так код функции bb() можно заменить на

 
Код:
void bb(){std::cout<<"BB"<<std::endl;};
и получать вполне осмысленный результат.
3
14 июля 2005 года
Green
4.8K / / 20.01.2000
Цитата:
Originally posted by solovey
так код функции bb() можно заменить на
 
Код:
void bb(){std::cout<<"BB"<<std::endl;};
и получать вполне осмысленный результат.



Ну если не обращаться к полям объекта, то проблем не будет.

1.9K
14 июля 2005 года
solovey
113 / / 25.07.2004
Цитата:
Originally posted by Green
Ну если не обращаться к полям объекта, то проблем не будет.

вот этого я и не понимаю. Я же создал объект класса А, почему у него имеется метод класса ВВ?

430
20 июля 2005 года
craftyfox
157 / / 20.02.2000
Цитата:
Originally posted by solovey
вот этого я и не понимаю. Я же создал объект класса А, почему у него имеется метод класса ВВ?


Потому что классы и объекты существуют только "на уровне исходного кода" (т.е. C++), в машинных кодах (почему я и показал тебе дизассемблер) все "глобальное". У тебя p - указатель на объект типа BB - компилятор и обращается с ним как с таковым, не заботясь о том, что у тебя там на самом деле.
Вот еще примерчик:

Код:
class A
{
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();
}

тоже вроде-бы "срабатывает", но ...
1.9K
20 июля 2005 года
solovey
113 / / 25.07.2004
Цитата:
Originally posted by craftyfox
Потому что классы и объекты существуют только "на уровне исходного кода" (т.е. C++), в машинных кодах (почему я и показал тебе дизассемблер) все "глобальное". У тебя p - указатель на объект типа BB - компилятор и обращается с ним как с таковым, не заботясь о том, что у тебя там на самом деле.
Вот еще примерчик:
Код:
class A
{
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()"? По идее программа должна на этапе выполнения либо вывалится с какой-нить ошибкой, либо выдать какой-нибудь мусор, т.к. в предполагаемом месте функции нет. Но почему-то ни того, ни другого не происходит.

430
20 июля 2005 года
craftyfox
157 / / 20.02.2000
Цитата:
Originally posted by solovey
я понимаю, что в машинном коде нет понятия "класс". Если я говорю компилятору "создай объект класса А и сохрани указатель на него в р", то компилятор так и должен сделать. Если я ему говорю "не делай проверки типов указателей, а просто присвой указателю на класс 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()).
Кстати, разьве Страуструпп этого не объясняет?
Слишком уж фундаментальные вопросы, чтобы узнавать от кого попало (:)) - смотри литературу.

1.9K
25 июля 2005 года
solovey
113 / / 25.07.2004
Цитата:
Originally posted by craftyfox
Не "указатель на класс", а "указатель на объект класса" (я не придираюсь к словам, в данном случае это существенно).
Объект этот можно представить в виде ассемблерной или 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()).
Кстати, разьве Страуструпп этого не объясняет?
Слишком уж фундаментальные вопросы, чтобы узнавать от кого попало (:)) - смотри литературу.

Спасибо, поищу у Страуструпа

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог