Множественное наследование: разминка для мозгов
От: Roman Fadeyev  
Дата: 06.02.02 06:41
Оценка: 1 (1)
Здравствуйте.

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

Ниже приведены три класса: два базовых и один их наследник


class A{
public:
    int a;
};
class B{
public:
    int b;
};

class C:public A,public B
{
public:
    void Test(){
        cout<<"a:"<<a<<"  b:"<<b;
    }
};


Теперь выполняем сей код:


int main(int argc, char* argv[])
{
    typedef void (B::*pClassB_Func)();
    C TestC;    //Итак, имеем класс C - наследник A+B
    TestC.a=1;  //Это поле от A
    TestC.b=2;  //Это поле от B
    pClassB_Func pf=static_cast<pClassB_Func>(C::Test); //Указатель на функцию  void C::Test()

    //Вот это, казалось бы безобидный вариант. Но неправильный
    B * pB=&TestC;
    (pB->*pf)();
    //Вот это очень ужасный на вид, но РАБОЧИЙ вариант
    pB=static_cast<B*>((void*)&TestC);
    (pB->*pf)();

    return 0;
}


Попробуйте предсказать, что выдаст функция в первом и втором варианте.

Кому в лом это компилить, скажу что первый вариант вызова функции выдаст что-то типа
"a:2 b:1234567". Т.е. адресация СДВИНУЛАСЬ на размер класса A, и поле a теперь на самом деле попадает на поле b, а поле b — сами видите куда.

Во втором случае все прекрасно.

Пораскинув мозгами, поведение компилятора можно понять: мы просили указатель на базовый класс B, он нам его и дал, слегка сместив. Во втором случае имеем передачу адреса (void*), и компилятор послушно ее выполняет.

Ответа я, собственно, не прошу. Просто хочу показать фишку. Ни в одном учебнике по С++, что я читал, такого примера не встречал, так что может кому сберегу лоб (см грабли)

Если у кого есть похожие байки, или он хочет чё-нить выразить по поводу сабжа, или хочет назвать меня ослом, буду рад выслушать
Re: Множественное наследование: разминка для мозгов
От: pdx Мальта http://dottedmag.net/
Дата: 06.02.02 07:30
Оценка:
Здравствуйте Roman Fadeyev, Вы писали:


RF>Ниже приведены три класса: два базовых и один их наследник


RF>
RF>class A{
RF>public:
RF>    int a;
RF>};
RF>class B{
RF>public:
RF>    int b;
RF>};

RF>class C:public A,public B
RF>{
RF>public:
RF>    void Test(){
RF>        cout<<"a:"<<a<<"  b:"<<b;
RF>    }
RF>};
RF>


RF>Теперь выполняем сей код:



RF>
RF>int main(int argc, char* argv[])
RF>{
RF>    typedef void (B::*pClassB_Func)();
RF>    C TestC;    //Итак, имеем класс C - наследник A+B
RF>    TestC.a=1;  //Это поле от A
RF>    TestC.b=2;  //Это поле от B
RF>    pClassB_Func pf=static_cast<pClassB_Func>(C::Test); //Указатель на функцию  void C::Test()

RF>    //Вот это, казалось бы безобидный вариант. Но неправильный
RF>    B * pB=&TestC;
RF>    (pB->*pf)();
RF>    //Вот это очень ужасный на вид, но РАБОЧИЙ вариант
RF>    pB=static_cast<B*>((void*)&TestC);
RF>    (pB->*pf)();

RF>    return 0;
RF>}
RF>


RF>Попробуйте предсказать, что выдаст функция в первом и втором варианте.


RF>Кому в лом это компилить, скажу что первый вариант вызова функции выдаст что-то типа

RF>"a:2 b:1234567". Т.е. адресация СДВИНУЛАСЬ на размер класса A, и поле a теперь на самом деле попадает на поле b, а поле b — сами видите куда.

RF>Во втором случае все прекрасно.


RF>Пораскинув мозгами, поведение компилятора можно понять: мы просили указатель на базовый класс B, он нам его и дал, слегка сместив. Во втором случае имеем передачу адреса (void*), и компилятор послушно ее выполняет.


RF>Ответа я, собственно, не прошу. Просто хочу показать фишку. Ни в одном учебнике по С++, что я читал, такого примера не встречал, так что может кому сберегу лоб (см грабли)


А такие вещи в учебниках не пишут. Тут нужно что-либо посерьезнее. Для примера, "C++ for Real Programmers" Элджера. Там все это хорошо прописано. И вообще книга полезная. Не для практического применения , а для расширения мировоззрения .
Re: Множественное наследование: разминка для мозгов
От: migel  
Дата: 06.02.02 07:31
Оценка:
Здравствуйте Roman Fadeyev, Вы писали:

RF>Здравствуйте.


RF>Когда-то я наступил на грабли множественного наследования. А сегодня, вспомнив об этом,

RF>хочу подсунуть их вам, подогреть, так сказать, форум.

RF>Если у кого есть похожие байки, или он хочет чё-нить выразить по поводу сабжа, или хочет назвать меня ослом, буду рад выслушать


Типичный синдром преобразование CLASS* -> void*->CLASS*
положение объекта в памяти при множественном (невиртуальном наследовании) можно вот так изобразить:
00 vTbl_pointerA
04 Class_A_data
....
xx vTbl_pointerB
xx+4 Class_B_data
yy Class_C_Data

поэтому при преобразованиях в void* вся адресация и едет (компилятор просто считает что ТЫ ЗНАЕШ ЧЕГО ТЫ ДЕЛАЕШ).
Отсюда МОРАЛЬ — аккуратнее товарищи с VOID* аккуратнее...
Re: Множественное наследование: разминка для мозгов
От: Ant  
Дата: 06.02.02 07:40
Оценка: 1 (1)
Здравствуйте Roman Fadeyev, Вы писали:

Кстати обрати внимание. Если изменить порядок базовых классов в наследнике, то все работает как надо в обоих вариантах.

class C:public B,public A
{
public:

...
};

:user:
Re[2]: Множественное наследование: разминка для мозгов
От: Roman Fadeyev  
Дата: 06.02.02 08:16
Оценка:
Здравствуйте Ant, Вы писали:

Ant>Здравствуйте Roman Fadeyev, Вы писали:


Ant>Кстати обрати внимание. Если изменить порядок базовых классов в наследнике, то все работает как надо в обоих вариантах.


Ant>class C:public B,public A

Ant>{
Ant>public:

Ant>...

Ant>};

Ant>


Ну понятно, в чем и прикол. Но чтоб твой вариант не работал, достаточно вместо B* использовать указатель A*
Re[3]: Множественное наследование: разминка для мозгов
От: Кодт Россия  
Дата: 07.02.02 09:38
Оценка: 6 (2) -1
Здравствуй ALL!

Примерчик-то — гремучий:

1) преобразовали &C::Test к void(*B::pfn)(void)
(а почему не к LPTHREADPROC, например?)

2) взяли произвольное смещение (приведение типа при множественном наследовании)

3) дернули за веревочку, дверка открылась

И вы хотели, чтобы об этом писали в учебниках.
В разделе "Нефиг баловаться с указателями".
Перекуём баги на фичи!
Re[2]: Множественное наследование: разминка для мозгов
От: Dr_Sh0ck Беларусь  
Дата: 07.02.02 10:57
Оценка:
Здравствуйте pdx, Вы писали:

pdx>А такие вещи в учебниках не пишут. Тут нужно что-либо посерьезнее. Для примера, "C++ for Real Programmers" Элджера. Там все это хорошо прописано. И вообще книга полезная. Не для практического применения , а для расширения мировоззрения .



А ты не знаешь, может ее где качнуть можно?
Do not fake yourself ;)
ICQ#: 198114726
Re: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 07.02.02 17:55
Оценка:
Здравствуйте Roman Fadeyev, Вы писали:

RF>Когда-то я наступил на грабли множественного наследования. А сегодня, вспомнив об этом,

RF>хочу подсунуть их вам, подогреть, так сказать, форум.

В GCC этого эффекта не будет. А вообще, это тут уже пробегало неделю назад.

http://www.rsdn.ru/forum/message.asp?mid=24570&amp;only
Автор: Андрей Тарасевич
Дата: 27.01.02
Best regards,
Андрей Тарасевич
Re[2]: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 07.02.02 17:58
Оценка: 15 (1)
Здравствуйте pdx, Вы писали:

pdx>А такие вещи в учебниках не пишут. Тут нужно что-либо посерьезнее. Для примера, "C++ for Real Programmers" Элджера. Там все это хорошо прописано. И вообще книга полезная. Не для практического применения , а для расширения мировоззрения .


Такие вещи в учебниках не пишут потому, что это баг компилятора MSVC++ 6. С чего бы это о нем стали писать в учебниках?
Best regards,
Андрей Тарасевич
Re[4]: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 07.02.02 18:01
Оценка:
Здравствуйте Кодт, Вы писали:

К>Примерчик-то — гремучий:


Примерчик совершенно корректный.

К>1) преобразовали &C::Test к void(*B::pfn)(void)

К>(а почему не к LPTHREADPROC, например?)

Потому что спеуцификация С++ не может предсказать поведение программы, которое получится после преобразования и вызова через LPTHREADPROC. А преобразование к 'void(*B::pfn)()' — совершенно легально и должно давать правильные резулитаты при таком способе вызова.

К>2) взяли произвольное смещение (приведение типа при множественном наследовании)


А приведение типа при множественном наследовании не берет произвольное смещение. Оно берет правильное смещение.
Best regards,
Андрей Тарасевич
Re: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 07.02.02 19:33
Оценка:
Здравствуйте Roman Fadeyev, Вы писали:

RF>> pClassB_Func pf=static_cast<pClassB_Func>(C::Test); //Указатель на функцию  void C::Test()

pClassB_Func pf=static_cast<pClassB_Func>(&C::Test);

RF>>    //Вот это, казалось бы безобидный вариант. Но неправильный
RF>>    B * pB=&TestC;
RF>>    (pB->*pf)();


Это правильный вариант. Но нерабочий, из-за некорректного поведения компилятора MSVC++ 6.

RF>>    //Вот это очень ужасный на вид, но РАБОЧИЙ вариант
RF>>    pB=static_cast<B*>((void*)&TestC);
RF>>    (pB->*pf)();


А вот это — неправильный вариант. Но, по чистой случайности, рабочий в данном конкретном случае.

RF>>Пораскинув мозгами, поведение компилятора можно понять: мы просили указатель на базовый класс B, он нам его и дал, слегка сместив.


После этого компилятор должен был заметить, что для правильного вызова метода через указатель 'pf' надо еще раз "слегка сместить" указатель 'pB'. И все дожно работать правльно. Но MSVC++ 6 этого не заметил.
Best regards,
Андрей Тарасевич
Re[2]: Множественное наследование: разминка для мозгов
От: Кодт Россия  
Дата: 13.02.02 08:44
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>
RF>>> pClassB_Func pf=static_cast<pClassB_Func>(C::Test); //Указатель на функцию  void C::Test()

АТ>pClassB_Func pf=static_cast<pClassB_Func>(&C::Test);

RF>>>    //Вот это, казалось бы безобидный вариант. Но неправильный
RF>>>    B * pB=&TestC;
RF>>>    (pB->*pf)();
АТ>


АТ>Это правильный вариант. Но нерабочий, из-за некорректного поведения компилятора MSVC++ 6.


АТ>
RF>>>    //Вот это очень ужасный на вид, но РАБОЧИЙ вариант
RF>>>    pB=static_cast<B*>((void*)&TestC);
RF>>>    (pB->*pf)();
АТ>


АТ>А вот это — неправильный вариант. Но, по чистой случайности, рабочий в данном конкретном случае.


RF>>>Пораскинув мозгами, поведение компилятора можно понять: мы просили указатель на базовый класс B, он нам его и дал, слегка сместив.


АТ>После этого компилятор должен был заметить, что для правильного вызова метода через указатель 'pf' надо еще раз "слегка сместить" указатель 'pB'. И все дожно работать правльно. Но MSVC++ 6 этого не заметил.


А с какой это радости он должен "смещать обратно"?
Имеем:
typedef void (B::*pClassB_Func)();
  // вызов функции класса B (про C нигде не сказано)

pClassB_Func pf=static_cast<pClassB_Func>(C::Test);
  // взяли адрес некой функции и громко крикнули, что это - член класса B

B * pB=&TestC;
  // привели тип (со смещением, разумеется)

(pB->*pf)();
  // где здесь сказано про С?!

Хорошо, все происходит в одном { }, компилятор еще может догадаться.
А если это размазано по программе?

Или static_cast<PFN> должен рожать шлюзовую функцию
void TestC_B_Thunk(B* ThisB)
{
  C* ThisC = (C*)((void*)ThisB + C::offset_of_B);
  return ThisC->TestC();
}

asm
{
@@TestC_B_Thunk:
  add [sp+4], C::offset_of_B
  jmp C::TestC
}

на которую передавать управление?
Перекуём баги на фичи!
Re: Множественное наследование: разминка для мозгов
От: Hollander Беларусь http://blogs.rsdn.org/ikemefula
Дата: 13.02.02 13:15
Оценка:
Здравствуйте Roman Fadeyev, Вы писали:

RF>Здравствуйте.



RF>
RF>int main(int argc, char* argv[])
RF>{
RF>    typedef void (B::*pClassB_Func)();

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

RF>    C TestC;    //Итак, имеем класс C - наследник A+B
RF>    TestC.a=1;  //Это поле от A
RF>    TestC.b=2;  //Это поле от B
RF>    pClassB_Func pf=static_cast<pClassB_Func>(C::Test); //Указатель на функцию  void C::Test()

RF>    //Вот это, казалось бы безобидный вариант. Но неправильный

Ну вы, блин, даете - конечно неправильный ! Функция то скомпилирована для класса C ! Ей и передать надо указатель на экземпляр от С ! This в процессе приведения типа корректируется постоянно. 

RF>    B * pB=&TestC;
RF>    (pB->*pf)(); - а приведение выполнять не должно, потому, что в typedef фигурирует B !

Сюда ты передал указатель на экземпляр B (последний унаследованный) , а функция то требует указатель на экземпляр С !

RF>    //Вот это очень ужасный на вид, но РАБОЧИЙ вариант
RF>    pB=static_cast<B*>((void*)&TestC);

Естественно, ты сам указал приведение типа.
RF>    (pB->*pf)();

RF>    return 0;
RF>}
RF>


Кстати, приведение типа для this делается 2мя способами (в случае множественного наследования) :

1 корректировка this вперед/назад ДО вызова — невиртуальные не статические функции, когда сразу все ясно
2 корректировка this вперед/назад после вызова (это можно проверить в дизассемблере и тд) — вставляется промежуточный код, который выполняет корректировку — это выполняется для особо хитрых случаев множественного наследования с виртуальными функциями и всякими интерфейсами.
Re[3]: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 13.02.02 16:48
Оценка:
Здравствуйте Кодт, Вы писали:

RF>>>>Пораскинув мозгами, поведение компилятора можно понять: мы просили указатель на базовый класс B, он нам его и дал, слегка сместив.


АТ>>После этого компилятор должен был заметить, что для правильного вызова метода через указатель 'pf' надо еще раз "слегка сместить" указатель 'pB'. И все дожно работать правльно. Но MSVC++ 6 этого не заметил.


К>А с какой это радости он должен "смещать обратно"?

К>Имеем:
К>
К>typedef void (B::*pClassB_Func)();
К>  // вызов функции класса B (про C нигде не сказано)

К>pClassB_Func pf=static_cast<pClassB_Func>(C::Test);
К>  // взяли адрес некой функции и громко крикнули, что это - член класса B

К>B * pB=&TestC;
К>  // привели тип (со смещением, разумеется)

(pB->>*pf)();
К>  // где здесь сказано про С?!
К>

К>Хорошо, все происходит в одном { }, компилятор еще может догадаться.
К>А если это размазано по программе?

Компилятор должен "смещать обратно" по той простой причине, что это требуется стандартом языка. А как он это делает — это уже его проблема.

К>Или static_cast<PFN> должен рожать шлюзовую функцию

К>[c]
К>void TestC_B_Thunk(B* ThisB)
К>{
К> C* ThisC = (C*)((void*)ThisB + C::offset_of_B);
К> return ThisC->TestC();
К>}

К>на которую передавать управление?


Нет, это делается проще. Тут уже говорилось об этом не раз. В составе указателя на метод, кроме самого указателя на точку входа в метод, хранится еще смещение класса B в составе класса С, котрое во всех таких ситуация позволяет вычислить правильное значение this при вызове метода. Поменяй установки проекта, как сказано здаесь

http://www.rsdn.ru/forum/message.asp?mid=28006&amp;only
Автор: Андрей Тарасевич
Дата: 11.02.02


и все будет прекрасно вызываться без всяких шлюзовых функций.
Best regards,
Андрей Тарасевич
Re[4]: Множественное наследование: разминка для мозгов
От: Кодт Россия  
Дата: 14.02.02 09:32
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

RF>>>>>Пораскинув мозгами, поведение компилятора можно понять: мы просили указатель на базовый класс B, он нам его и дал, слегка сместив.


АТ>>>После этого компилятор должен был заметить, что для правильного вызова метода через указатель 'pf' надо еще раз "слегка сместить" указатель 'pB'. И все дожно работать правльно. Но MSVC++ 6 этого не заметил.


К>>А с какой это радости он должен "смещать обратно"?


АТ>Компилятор должен "смещать обратно" по той простой причине, что это требуется стандартом языка. А как он это делает — это уже его проблема.


К>>Или static_cast<PFN> должен рожать шлюзовую функцию

К>>void TestC_B_Thunk(B* ThisB)
К>>{
К>>  C* ThisC = (C*)((void*)ThisB + C::offset_of_B);
К>>  return ThisC->TestC();
К>>}

К>>на которую передавать управление?

АТ>Нет, это делается проще. Тут уже говорилось об этом не раз. В составе указателя на метод, кроме самого указателя на точку входа в метод, хранится еще смещение класса B в составе класса С, котрое во всех таких ситуация позволяет вычислить правильное значение this при вызове метода. Поменяй установки проекта, как сказано здаесь


АТ>http://www.rsdn.ru/forum/message.asp?mid=28006&amp;only
Автор: Андрей Тарасевич
Дата: 11.02.02


АТ>и все будет прекрасно вызываться без всяких шлюзовых функций.


Даа... интересно.
Может, заодно объясните (или подскажете — где глянуть), как этот сдвоенный указатель выглядит и как с ним работают?

Кстати, насколько я понимаю, такой цирк не прокатит с COM, потому что указатели там передаются одинарные ([in]IUnknown*pUnk, [out]void**ppV, и тому подобное).

И еще. В каких случаях реально требуется жонглировать ссылками на методы & субклассы? (может, это свидетельство архитектурных недоработок)
Перекуём баги на фичи!
Re[2]: Множественное наследование: разминка для мозгов
От: Аноним  
Дата: 14.02.02 18:11
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>Здравствуйте Roman Fadeyev, Вы писали:


RF>>Когда-то я наступил на грабли множественного наследования. А сегодня, вспомнив об этом,

RF>>хочу подсунуть их вам, подогреть, так сказать, форум.

АТ>В GCC этого эффекта не будет. А вообще, это тут уже пробегало неделю назад.


АТ>http://www.rsdn.ru/forum/message.asp?mid=24570&amp;only
Автор: Андрей Тарасевич
Дата: 27.01.02


Выдержки из "C++ Tip-of-the-Day":
TITLE: Casting pointer to member functions

PROBLEM: Randy Chapman <chapman@u.washington.edu>

I am trying to get some old (1991) code working, and it appears that
current versions of C++ break it :(

The basic juist of the problem is that I have a pointer type that is
a pointer to a member function of class A.  I want to assign to
that variable (a member of class A, via a SetCallback()),
a member function pointer of class B (same paramters, etc).
Compiler (g++) gives me a casting error:

> cat foo.C

class A;

typedef void (A::*Aptr)();

class A {
  Aptr foo;
public:
        void SetFoo(char *event, Aptr fooP) { foo = fooP; }
};

class B: protected A {
        void junk();
        B() { SetFoo("SomeOddThing",junk); }
};

> gcc foo.C
foo.C: In method `B::B()':
foo.C:14: type `B' is not a base type for type `A'
foo.C:14:    in pointer to member function conversion


RESPONSE: clamage@Eng.Sun.COM (Steve Clamage), 7 Jun 95

First, you have a syntax error. The call to SetFoo should look like
        B() { SetFoo("SomeOddThing", &B::junk); }

Second, casting among pointer-to-member-function works backwards from
ordinary casts. Normally, you can cast a derived to a base, since a
derived is a base (loosely speaking).

But with pointer-to-member-function, the reverse is the case. A
pointer to a Derived member function cannot be used where a pointer
to Base member function is expected. The Base::*ptr will be used
in conjunction with Base object, and a member of class Derived
will expect to operate on a Derived object. In general, this will
lead to disaster. (E.g., the Derived member function stores into data
which is part of a Derived object, but isn't part of a Base object.)
That's why it isn't allowed.
Re[2]: Множественное наследование: разминка для мозгов
От: Pavel Ivlev www.vsi.ru/~pavel
Дата: 14.02.02 18:17
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>Здравствуйте Roman Fadeyev, Вы писали:


RF>>Когда-то я наступил на грабли множественного наследования. А сегодня, вспомнив об этом,

RF>>хочу подсунуть их вам, подогреть, так сказать, форум.

АТ>В GCC этого эффекта не будет. А вообще, это тут уже пробегало неделю назад.


АТ>http://www.rsdn.ru/forum/message.asp?mid=24570&amp;only
Автор: Андрей Тарасевич
Дата: 27.01.02


Выдержки из "C++ Tip-of-the-Day":
TITLE: Casting pointer to member functions

PROBLEM: Randy Chapman <chapman@u.washington.edu>

I am trying to get some old (1991) code working, and it appears that
current versions of C++ break it :(

The basic juist of the problem is that I have a pointer type that is
a pointer to a member function of class A.  I want to assign to
that variable (a member of class A, via a SetCallback()),
a member function pointer of class B (same paramters, etc).
Compiler (g++) gives me a casting error:

> cat foo.C

class A;

typedef void (A::*Aptr)();

class A {
  Aptr foo;
public:
        void SetFoo(char *event, Aptr fooP) { foo = fooP; }
};

class B: protected A {
        void junk();
        B() { SetFoo("SomeOddThing",junk); }
};

> gcc foo.C
foo.C: In method `B::B()':
foo.C:14: type `B' is not a base type for type `A'
foo.C:14:    in pointer to member function conversion


RESPONSE: clamage@Eng.Sun.COM (Steve Clamage), 7 Jun 95

First, you have a syntax error. The call to SetFoo should look like
        B() { SetFoo("SomeOddThing", &B::junk); }

Second, casting among pointer-to-member-function works backwards from
ordinary casts. Normally, you can cast a derived to a base, since a
derived is a base (loosely speaking).

But with pointer-to-member-function, the reverse is the case. A
pointer to a Derived member function cannot be used where a pointer
to Base member function is expected. The Base::*ptr will be used
in conjunction with Base object, and a member of class Derived
will expect to operate on a Derived object. In general, this will
lead to disaster. (E.g., the Derived member function stores into data
which is part of a Derived object, but isn't part of a Base object.)
That's why it isn't allowed.
Re[3]: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 14.02.02 18:59
Оценка: 12 (1)
Здравствуйте Pavel Ivlev, Вы писали:

RF>>>Когда-то я наступил на грабли множественного наследования. А сегодня, вспомнив об этом,

RF>>>хочу подсунуть их вам, подогреть, так сказать, форум.

АТ>>В GCC этого эффекта не будет. А вообще, это тут уже пробегало неделю назад.


АТ>>http://www.rsdn.ru/forum/message.asp?mid=24570&amp;only
Автор: Андрей Тарасевич
Дата: 27.01.02


А>Выдержки из "C++ Tip-of-the-Day":
А>[code]
А>RESPONSE: clamage@Eng.Sun.COM (Steve Clamage), 7 Jun 95


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

А>First, you have a syntax error. The call to SetFoo should look like
А>        B() { SetFoo("SomeOddThing", &B::junk); }


Совершенно верно, оператор '&' для получения адреса метода необходим.

А>Second, casting among pointer-to-member-function works backwards from
А>ordinary casts. Normally, you can cast a derived to a base, since a
А>derived is a base (loosely speaking).
А>But with pointer-to-member-function, the reverse is the case. A
А>pointer to a Derived member function cannot be used where a pointer
А>to Base member function is expected.


Совершенно верно, но важным моментом является то, что ситуацию "pointer to Base member function is expected" компилятор в общем случае проверить не в состоянии (cм. ниже)

A>The Base::*ptr will be used
А>in conjunction with Base object, and a member of class Derived
А>will expect to operate on a Derived object.


Да, именно так.

A>In general, this will
А>lead to disaster.


Неверно. In general, this may lead to disaster. Но совсем не обязательно, что это will lead to disaster. В С++ очень много возможностей, неправильное использование которых потенциально может привести к катастрофе. Тем не менее их приходится использовать.

A>(E.g., the Derived member function stores into data
А>which is part of a Derived object, but isn't part of a Base object.)
А>That's why it isn't allowed.


Неверно. Конверсия от указателя на метод класса-потомка к указателю на метод класса-предка в С++ разрешена. Другое дело, что она не является стандартной конверсией языка C++, т.е. не делается неявно. Поэтому данный пример и не компилируется. Однако, эта конверсия может быть совершенно легально выполнена при помощи явного приведения типов (через 'static_cast'). Код надо исправить так:

B() { SetFoo("SomeOddThing", static_cast<Aptr>(&B::junk)); }


И все будет прекрасно компилироваться.

Да, Steve Clamage прав в том, что такие конверсии опасны тем, что при вызове метода через такой указатель, компилятор не всегда имеет возможность проверить, что данный экземпляр класса A является частью класса B. Если вдруг окажется, что это не так, то такой вызов приведет к катастрофе. Тем не менее стандарт С++ такую конверсию не запрещает. За правильностью вызовов при этом предлагается следить программисту. По крайней мере в примере кода, присутствовавшем в постановке вопроса, делалась совершенно безопасная инициализация указателя. Ни о каком "it isn't allowed" здесь речи быть не может.
Best regards,
Андрей Тарасевич
Re[4]: Множественное наследование: разминка для мозгов
От: Pavel Ivlev www.vsi.ru/~pavel
Дата: 14.02.02 20:54
Оценка: -1
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>В своем ответе Steve Clamage решил самостоятельно логически порассуждать и в результате он пришел к приведенным результатам, которые ему кажутся правильными и естественными. В этом нет ничего плохого. Но для того, чтобы дать полный ответ на вопрос, ему все-таки не мешало заглянуть в стандарт языка.


Самоувененность Ваша не знает границ.
Из книги Б. Страуструпа "Дизайн и Эволюция языка С++":
"Благодарности<br>Я выражаю глубокую признательность Стиву Клэмиджу (Steve Clamage), Тони Хансену (Tony Hansen)...."
Как видите, имя Стива стоит на первом месте, а вот имени Андрей Тарасевич в том списке нет.

С другой стороны, Страуструп пишет:
"... we can safely assign a pointer to a member of a base class to a pointer to a member of a derived class, but not the other way around. This property also called contravariance. ... This contravariance rule appears to be the opposite of the rule that says we can assign a pointer to a derived class to a pointer to its base class. In fact, both rules exist to preserve fundamental guarantee that a pointer to an object that doesen't at least have properties that the pointer promises. ... By refusing the initialisation, the compiler saves us from run-time error."

Таком образом, в стандарт языка следует заглядывать не Стиву Клэмиджу.




--
In general, this will lead to disaster.


АТ>Неверно. In general, this may lead to disaster. Но совсем не обязательно, что это will lead to disaster.


Для тех, кто не ладит с английским, перевожу:
В общем случае, это приведет к беде.

Но, не в любом случае; не во всех случаях.
Re[5]: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 14.02.02 21:58
Оценка: 8 (2)
Здравствуйте Pavel Ivlev, Вы писали:

АТ>>В своем ответе Steve Clamage решил самостоятельно логически порассуждать и в результате он пришел к приведенным результатам, которые ему кажутся правильными и естественными. В этом нет ничего плохого. Но для того, чтобы дать полный ответ на вопрос, ему все-таки не мешало заглянуть в стандарт языка.


PI>Самоувененность Ваша не знает границ.


А тут нет никакой самоуверенности. Спецификация языка С++ — объективна. Если мое утверждение согласуется со стандартом языка, а утверждение Steve Clamage ему противоречит, то я прав, а он — нет. Тут все просто.

А вот и выдержка из стандарта языка:

5.2.9 Static cast
...
9 An rvalue of type "pointer to member of D of type cv1 T", can be converted to an rvalue of type "pointer to member of B of type cv2 T", where B is a base class of D, if a valid standard conversion from "pointer to member of B of type T" to "pointer to member of D of type T" exists, and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1....

Вопрос закрыт.

PI>Из книги Б. Страуструпа "Дизайн и Эволюция языка С++":

PI>"Благодарности<br>Я выражаю глубокую признательность Стиву Клэмиджу (Steve Clamage), Тони Хансену (Tony Hansen)...."
PI>Как видите, имя Стива стоит на первом месте, а вот имени Андрей Тарасевич в том списке нет.

А это совершенно не имеет никакого значения. Еще раз — спецификация языка С++ — объективна. И истинность утверждений в этой области определяется только из соответствия утверждения этой спецификации. Популярность имени утверждаюшего и его личный авторитет никакого значения не имеют.

И не надо совать мне в нос сравнения моего авторитета с авторитетом Стива Клэмиджа. Я не могу участвовать в проведении такого сравнения из соображений обычной порядочности.

PI>С другой стороны, Страуструп пишет:

PI>"... we can safely assign a pointer to a member of a base class to a pointer to a member of a derived class, but not the other way around. This property also called contravariance. ... This contravariance rule appears to be the opposite of the rule that says we can assign a pointer to a derived class to a pointer to its base class. In fact, both rules exist to preserve fundamental guarantee that a pointer to an object that doesen't at least have properties that the pointer promises. ... By refusing the initialisation, the compiler saves us from run-time error."

Это потому, что ты еще не научился читать такие книги. В С++, как и во многих языках программирования, есть как минимум два типа ституации "нельзя так делать":

— нельзя так делать, потому что это нелегально с точки зрения С++
— нельзя так делать, потому что это может быть опасно, хотя и легально с точки зрения C++

Читая книги от Stroustrup, Herb Sutter и т.д. ты дожен иметь в в виду, что эти авторы зачастую чересчур сильно ориентирутся на начинаюших программистов и заменяют эти два типа "нельзя" на один — просто "нельзя и все тут". Они в некотром смысле в этом правы — в таких ситуациях объяснить начинающему тонкие различия между первым и вторым вариантом бывает сложно и, скорее всего, подробное объяснение только запутает читателя. Точно таким же упрощением страдает приведенное объяснение от Стива Клэмиджа. (Книги Скотта Мейерса, кстати, страдают от этого в намного меньшей степени.)

А профессионал должен уметь различать такие ситуации и понимать, что имеется в виду под каждым конкретным "нельзя" и что второе "нельзя" не является догмой и при разумном и грамотном использовании оно превращается в "можно" и даже "нужно".

PI>Таком образом, в стандарт языка следует заглядывать не Стиву Клэмиджу.


Текст ответа Стива на этот вопрос датируется аж 1995 годом

http://groups.google.com/groups?selm=3r4gjs%24p0l%40engnews2.Eng.Sun.COM&amp;output=gplain

когда завершенного стандарта у языка С++ еще не было.

Как мы уже убедились, в стандарт языка Стиву Клэмиджу (образца 1995 года :) ) все таки стоит заглянуть. По крайней мере в финальную его версию. Точнее, я уверен, что кто-кто, а Стив Клэмидж прекрасно ориентируется в финальной версии стандарта С++. А вот твои представления о С++, мягко говоря, обросли мхом.
Best regards,
Андрей Тарасевич
Re[6]: Множественное наследование: разминка для мозгов
От: Pavel Ivlev www.vsi.ru/~pavel
Дата: 15.02.02 09:43
Оценка: -1
Самоуверенность заключается в том, что ты отсылаешь авторитета за справочником. Новые операторы приведения были одобрены на заседании комитета в Сан-Хосе, в ноябре 1993 года.

АТ>Если мое утверждение согласуется со стандартом языка, а утверждение Steve Clamage ему противоречит, то я прав, а он — нет. Тут все просто.


Ты так ничего и не понял. Проблема не в том, как заставить компилятор замолчать (это можно сделать почти всегда), но в том, чтобы предвидеть проблемы, которые могут возникнуть во время исполнения (см. Струаструп).

АТ>А вот и выдержка из стандарта языка:


АТ>5.2.9 Static cast

АТ>...
АТ>9 An rvalue of type "pointer to member of D of type cv1 T", can be converted to an rvalue of type "pointer to member of B of type cv2 T", where B is a base class of D, if a valid standard conversion from "pointer to member of B of type T" to "pointer to member of D of type T" exists, and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1....

АТ>Вопрос закрыт.


Для самоуверенных слепцов. Ты не привел [Замечание] из того же параграфа 9 части 5.2.9 стандарта, оно гласит:
[Note: although class B need not contain the original member, the dynamic type of the object on which the pointer to member is dereferenced must contain the original member; see 5.5. ]

В свою очередь, в параграфе 4 пункта 5.5 сказано:
If the dynamic type of the object does not contain the member to which the pointer refers, the behavior is undefined.

возвращаясь к примеру в сообщении, породившему это дерево, имеем:

B* b = &TestC;

здесь b указывает на подобъект B класса C. У класса B нет члена, на который указывает pf. Этого требует известный (или нет?) тебе стандарт. Все работает верно и в случае с
B* b = static_cast<B*>((void*)&TestC)
.

АТ>А это совершенно не имеет никакого значения. Еще раз — спецификация языка С++ — объективна. И истинность утверждений в этой области определяется только из соответствия утверждения этой спецификации. Популярность имени утверждаюшего и его личный авторитет никакого значения не имеют.


Это имеет значение. Пержде, чем начать критиковать авторитет, надо долго думать, затем еще раз думать. И если (что маловероятно) тебе покажется, что ты прав, тогда задумайся: где ты ошибся.

АТ>И не надо совать мне в нос сравнения моего авторитета с авторитетом Стива Клэмиджа. Я не могу участвовать в проведении такого сравнения из соображений обычной порядочности.


Ну это понятно. А если учесть, что сам Стив Клэмидж не принимает в этом участие, то станет очевидным КПД подобного занятия.

АТ>Это потому, что ты еще не научился читать такие книги. В С++, как и во многих языках программирования, есть как минимум два типа ституации "нельзя так делать":


Вот, когда через много лет, ты напишешь свою книгу, мы поговорим о том, как ее надо уметь читать.

P.S.
Может для кого-то это покажется маловажным, но код, выдаваемый компилятором Borland в примере с TestC, дает те же результаты, что и код, выдаваемый MSVC++.
Re[7]: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 15.02.02 16:46
Оценка: 2 (1)
Здравствуйте Pavel Ivlev, Вы писали:

PI>Самоуверенность заключается в том, что ты отсылаешь авторитета за справочником. Новые операторы приведения были одобрены на заседании комитета в Сан-Хосе, в ноябре 1993 года.


АТ>>Если мое утверждение согласуется со стандартом языка, а утверждение Steve Clamage ему противоречит, то я прав, а он — нет. Тут все просто.


PI>Ты так ничего и не понял. Проблема не в том, как заставить компилятор замолчать (это можно сделать почти всегда), но в том, чтобы предвидеть проблемы, которые могут возникнуть во время исполнения (см. Струаструп).


А зачем ты сейчас об этом говоришь? С этим никто не спорит и об этом речь не идет. Стив в своем ответе сделал вполне конкретное безапелляционное заявление "This is not allowed". Это не так.

АТ>>А вот и выдержка из стандарта языка:


АТ>>5.2.9 Static cast

АТ>>...
АТ>>9 An rvalue of type "pointer to member of D of type cv1 T", can be converted to an rvalue of type "pointer to member of B of type cv2 T", where B is a base class of D, if a valid standard conversion from "pointer to member of B of type T" to "pointer to member of D of type T" exists, and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1....

АТ>>Вопрос закрыт.


PI>Для самоуверенных слепцов. Ты не привел [Замечание] из того же параграфа 9 части 5.2.9 стандарта, оно гласит:

PI>[Note: although class B need not contain the original member, the dynamic type of the object on which the pointer to member is dereferenced must contain the original member; see 5.5. ]

А не надо "переводить стрелки". Это замечание я здесь уже не раз приводил (тема указателей на методы в множественном наследовании тянутся тут уже долго). Но в данном конкретном случае (сообщение Стива Клэмиджа) оно ничего не меняет. В примере, который содержится в вопросе, динамический тип объекта корректен и никаких проблем не возникает.

PI>В свою очередь, в параграфе 4 пункта 5.5 сказано:

PI>If the dynamic type of the object does not contain the member to which the pointer refers, the behavior is undefined.

Ты понимаешь, что такое 'dynamic type'? Я вижу, что не понимаешь.

PI>возвращаясь к примеру в сообщении, породившему это дерево, имеем:


PI>
B* b = &TestC;

PI>здесь b указывает на подобъект B класса C. У класса B нет члена, на который указывает pf. Этого требует известный (или нет?) тебе стандарт.

В переводе на язык С++ "b указывает на подобъект B класса C" означает, что статический тип объекта '*b' равен B, а динамический тип объекта '*b' равен C. У класса С есть член, на который указывет 'pf', поэтому все требования стандарта тут строго соблюдены.

Если у тебя этот пример "не работает", это означает, что твой компилятор глючит. Здесь уже подробено объяснялось, почему такое может происходить в MSVC++. Поставь правильные установки компиляцмии, и все заработает. В GCC все это прекравно работает сразу.

PI> Все работает верно и в случае с
B* b = static_cast<B*>((void*)&TestC)
.


Вот это и есть пример кода, который ни в коем случае нельзя использовать в своих программах. В таком случае, если что-то и "работает", то по чисто случайным причинам. Уже указывалось, что если поменять порядок следования баз класса С, то он перестанет "работать". Это во-первых. А во-вторых, стандарт С++ вообше не гарантирует, что хотя бы одна из баз класса имеет тот же адрес, что и сам экземпляр класса. Этот код дает undefined behavior.

АТ>>Это потому, что ты еще не научился читать такие книги. В С++, как и во многих языках программирования, есть как минимум два типа ституации "нельзя так делать":


PI>P.S.

PI>Может для кого-то это покажется маловажным, но код, выдаваемый компилятором Borland в примере с TestC, дает те же результаты, что и код, выдаваемый MSVC++.

У тебя работоспособность этого примера у MSVC++ "зарублена" неправильными установками компиляции. Поставь правильные — и код перестанет "совпадать". Я не знаю, есть ли у Борланда установки, позволяющие заставить работать этот пример.
Best regards,
Андрей Тарасевич
Re[7]: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 15.02.02 16:52
Оценка:
Здравствуйте Pavel Ivlev, Вы писали:

PI>Самоуверенность заключается в том, что ты отсылаешь авторитета за справочником. Новые операторы приведения были одобрены на заседании комитета в Сан-Хосе, в ноябре 1993 года.


Операторы приведения — одно, спецификация этих операторов — другое. Класс 'std::auto_ptr' тоже был введен в состав STL давно. А вот его спецификация устоялась только в последний момент перед выходом стандарта.

А даже если нынешняя спецификация была принята еще в 1993, то это означает, что в 1995 Стив Клэмидж был просто напросто не прав, говоря, что "It is not allowed".
Best regards,
Андрей Тарасевич
Re[8]: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 15.02.02 18:28
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

PI>>Самоуверенность заключается в том, что ты отсылаешь авторитета за справочником. Новые операторы приведения были одобрены на заседании комитета в Сан-Хосе, в ноябре 1993 года.


АТ>Операторы приведения — одно, спецификация этих операторов — другое. Класс 'std::auto_ptr' тоже был введен в состав STL давно. А вот его спецификация устоялась только в последний момент перед выходом стандарта.


АТ>А даже если нынешняя спецификация была принята еще в 1993, то это означает, что в 1995 Стив Клэмидж был просто напросто не прав, говоря, что "It is not allowed".


А вот тебе и подтверждение того, что я был прав. Возьмем первый draft стандарта С++ (28 апреля 1995) и посмотрим на соответствующий пункт там:

5.2.8 Static cast
[...]
9 An rvalue of type "pointer to member of D of type cv1 T" can be converted to an rvalue of type "pointer to member of B of type cv2 T", where B is a base class of D, if a valid standard conversion from "pointer to member of B of type cv2 T" to "pointer to member of D of type cv2 T" exists, and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. [...] If class B contains or inherits the original member, the resulting pointer to member points to the member in class B. Otherwise, the result of the cast is undefined.

(Сравни выдеделенные строки с соотв. строками финальной версии стандарта или даже второго драфта)

Надеюсь теперь тебе понятно, на что опирался Стив Клэмидж, отвечая на тот вопрос в 1995 году?

Лирическое отступление: тебе, на твоем уровне владения С++, еше рановато выступать в публичных С++ форумах с наездами на других людей. Особенно на тех, кто разбирается в вопросе лучше тебя. Более того, тебе пока еще рановато приводить в подтверждение своих утверждений цитаты от авторитетов С++, потому что ты пока не в состоянии правильно понять что именно они говорят и почему они это говорят.
Best regards,
Андрей Тарасевич
Re[9]: Множественное наследование: разминка для мозгов
От: Alex Fedotov США  
Дата: 15.02.02 19:20
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>Лирическое отступление: тебе, на твоем уровне владения С++, еше рановато выступать в публичных С++ форумах с наездами на других людей. Особенно на тех, кто разбирается в вопросе лучше тебя. Более того, тебе пока еще рановато приводить в подтверждение своих утверждений цитаты от авторитетов С++, потому что ты пока не в состоянии правильно понять что именно они говорят и почему они это говорят.


Андрей, пожалуйста, не надо переносить методы ведения дискуссии R.Z1 сюда. Спасибо.
-- Alex Fedotov
Re[10]: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 15.02.02 19:40
Оценка:
Здравствуйте Alex Fedotov, Вы писали:

AF>Андрей, пожалуйста, не надо переносить методы ведения дискуссии R.Z1 сюда. Спасибо.


В этой дискуссии, надо сказать, вышеупомянутые методы явственно проступили несколько раньше, чем я их сюда принес...
Best regards,
Андрей Тарасевич
Re[8]: Множественное наследование: разминка для мозгов
От: Pavel Ivlev www.vsi.ru/~pavel
Дата: 15.02.02 20:06
Оценка: -1
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>А зачем ты сейчас об этом говоришь? С этим никто не спорит и об этом речь не идет. Стив в своем ответе сделал вполне конкретное безапелляционное заявление "This is not allowed". Это не так.


Я привел сообщение Стива Клэмиджа в ответ твоему заявлению о неработоспособности компилятора в примере с TestC. "This is not allowed",- это так! хотя бы потому что компилятор это не пропускает. Это также "not allowed", как и не допустимо изменение константной строки. Но ты можешь пойти в обход системы типов и компилятор позволит тебе это сделать.

АТ>А не надо "переводить стрелки". Это замечание я здесь уже не раз приводил (тема указателей на методы в множественном наследовании тянутся тут уже долго). Но в данном конкретном случае (сообщение Стива Клэмиджа) оно ничего не меняет. В примере, который содержится в вопросе, динамический тип объекта корректен и никаких проблем не возникает.


Я стараюсь быть прямолинейным, насколько это возможно. Если ты не понял, для чего я привел Стива Клэмиджа, возможно я виноват.

АТ>Ты понимаешь, что такое 'dynamic type'? Я вижу, что не понимаешь.


Самовлюленность тебя ослепляет.

АТ>В переводе на язык С++ "b указывает на подобъект B класса C" означает, что статический тип объекта '*b' равен B, а динамический тип объекта '*b' равен C. У класса С есть член, на который указывет 'pf', поэтому все требования стандарта тут строго соблюдены.


У тебя проблемы с переводом не только с английского языка: это верно в случае с lvalue. т.е.
B* b; C c; *b = c;
В случае с rvalue (наш случай) "The dynamic type of an rvalue expression is its static type."

АТ>Если у тебя этот пример "не работает", это означает, что твой компилятор глючит.


Это значит, что ты не понимаешь: почему он "не работает".

PI>> Все работает верно и в случае с
B* b = static_cast<B*>((void*)&TestC)
.


АТ>Вот это и есть пример кода, который ни в коем случае нельзя использовать в своих программах. В таком случае, если что-то и "работает", то по чисто случайным причинам. Уже указывалось, что если поменять порядок следования баз класса С, то он перестанет "работать". Это во-первых. А во-вторых, стандарт С++ вообше не гарантирует, что хотя бы одна из баз класса имеет тот же адрес, что и сам экземпляр класса. Этот код дает undefined behavior.


Этот код работает, потому что b начинает указывать на начало обекта TestC, не на подобъект B класса С. И, разумеется, не важен порядок следования базовых классов.
Re[8]: Множественное наследование: разминка для мозгов
От: Pavel Ivlev www.vsi.ru/~pavel
Дата: 15.02.02 20:06
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>Операторы приведения — одно, спецификация этих операторов — другое. Класс 'std::auto_ptr' тоже был введен в состав STL давно. А вот его спецификация устоялась только в последний момент перед выходом стандарта.


Тебе просто нужно прочитать книгу Страуструпа D&E. Там ты узнаешь про работу комитета (глава 6.2).
Re[9]: Множественное наследование: разминка для мозгов
От: Pavel Ivlev www.vsi.ru/~pavel
Дата: 15.02.02 20:07
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

АТ>А вот тебе и подтверждение того, что я был прав.


Лед тронулся.

AT>Возьмем первый draft стандарта С++ (28 апреля 1995) и посмотрим на соответствующий пункт там:


АТ>5.2.8 Static cast

АТ>[...]
АТ>9 An rvalue of type "pointer to member of D of type cv1 T" can be converted to an rvalue of type "pointer to member of B of type cv2 T", where B is a base class of D, if a valid standard conversion from "pointer to member of B of type cv2 T" to "pointer to member of D of type cv2 T" exists, and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. [...] If class B contains or inherits the original member, the resulting pointer to member points to the member in class B. Otherwise, the result of the cast is undefined.

АТ>(Сравни выдеделенные строки с соотв. строками финальной версии стандарта или даже второго драфта)


Я незнаю: с чем ты это сравниваешь, но у меня стандарт от 1998-09-01, и там написано:
"An rvalue of type “pointer to member of D of type cv1 T” can be converted to an rvalue of type “pointer to
member of B of type cv2 T”, where B is a base class (10) of D, if a valid standard conversion from “pointer
to member of B of type T” to “pointer to member of D of type T” exists (4.11), and cv2 is the same cvqualification
as, or greater cvqualification
than, cv1.59) The null member pointer value (4.11) is converted
to the null member pointer value of the destination type. If class B contains the original member, or is a
base or derived class of the class containing the original member, the resulting pointer to member points to
the original member. Otherwise, the result of the cast is undefined.
[Note: although class B need not contain
the original member, the dynamic type of the object on which the pointer to member is dereferenced
must contain the original member; see 5.5. ]"

По-твоему семантика различна?

АТ>Лирическое отступление: тебе, на твоем уровне владения С++, еше рановато выступать в публичных С++ форумах с наездами на других людей. Особенно на тех, кто разбирается в вопросе лучше тебя. Более того, тебе пока еще рановато приводить в подтверждение своих утверждений цитаты от авторитетов С++, потому что ты пока не в состоянии правильно понять что именно они говорят и почему они это говорят.


С поэзией у тебя не так хорошо, как с самовлюбленностью.
Re[10]: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 15.02.02 20:34
Оценка:
Здравствуйте Pavel Ivlev, Вы писали:

AT>>Возьмем первый draft стандарта С++ (28 апреля 1995) и посмотрим на соответствующий пункт там:


АТ>>5.2.8 Static cast

АТ>>[...]
АТ>>9 An rvalue of type "pointer to member of D of type cv1 T" can be converted to an rvalue of type "pointer to member of B of type cv2 T", where B is a base class of D, if a valid standard conversion from "pointer to member of B of type cv2 T" to "pointer to member of D of type cv2 T" exists, and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. [...] If class B contains or inherits the original member, the resulting pointer to member points to the member in class B. Otherwise, the result of the cast is undefined.

АТ>>(Сравни выдеделенные строки с соотв. строками финальной версии стандарта или даже второго драфта)


PI>Я незнаю: с чем ты это сравниваешь, но у меня стандарт от 1998-09-01, и там написано:

PI>"An rvalue of type “pointer to member of D of type cv1 T” can be converted to an rvalue of type “pointer to
PI>member of B of type cv2 T”, where B is a base class (10) of D, if a valid standard conversion from “pointer
PI>to member of B of type T” to “pointer to member of D of type T” exists (4.11), and cv2 is the same cvqualification
PI>as, or greater cvqualification
PI>than, cv1.59) The null member pointer value (4.11) is converted
PI>to the null member pointer value of the destination type. If class B contains the original member, or is a
PI>base or derived class of the class containing the original member, the resulting pointer to member points to
PI>the original member. Otherwise, the result of the cast is undefined.
[Note: although class B need not contain
PI>the original member, the dynamic type of the object on which the pointer to member is dereferenced
PI>must contain the original member; see 5.5. ]"

PI>По-твоему семантика различна?


Открой глаза! Ты не видишь разницы??? Семантика принципиально различна! (Самое время припомнить твое "для тех, кто не ладит с английским")

Draft:

If class B contains or inherits the original member, the resulting pointer to member points to the member in class B. Otherwise, the result of the cast is undefined.

Final:

If class B contains the original member, or is a base or derived class of the class containing the original member, the resulting pointer to member points to the original member. Otherwise, the result of the cast is undefined. [Note: although class B need not contain the original member, the dynamic type of the object on which the pointer to member is dereferenced must contain the original member; see 5.5. ]

В драфте требуется наличие данного члена в классе B (явно или унаследованно). В финальной версии его наличие в классе B не требуется, вместо этого тебуется его наличие в динамическом типе указуемого объекта.

С точки зрения драфта, Стив Клэмидж в своем ответе был совершенно прав. С точки зрения финальной версии — нет.
Best regards,
Андрей Тарасевич
Re[9]: Множественное наследование: разминка для мозгов
От: Андрей Тарасевич Беларусь  
Дата: 15.02.02 21:13
Оценка:
Здравствуйте Pavel Ivlev, Вы писали:

АТ>>А зачем ты сейчас об этом говоришь? С этим никто не спорит и об этом речь не идет. Стив в своем ответе сделал вполне конкретное безапелляционное заявление "This is not allowed". Это не так.


PI>Я привел сообщение Стива Клэмиджа в ответ твоему заявлению о неработоспособности компилятора в примере с TestC. "This is not allowed",- это так! хотя бы потому что компилятор это не пропускает. Это также "not allowed", как и не допустимо изменение константной строки. Но ты можешь пойти в обход системы типов и компилятор позволит тебе это сделать.


Нет. Попытка изменения константной строки приводит к неопределенному поведению. Это действительно not allowed. А приведние типа, о котром мы говорим, не только разрешено, но еше и четко определено (при соблюдении ряда условий).

Ты сам привел цитату из Страуструпа (и Клэмиджа) о том, что ситуация с указателями на методы в чем-то обратна ситуации с классами (у Страуструпа это называется contravariance). Приведение типа от указателя на потомка к указателю на предка (вверх по иерархии) является стадартной конверсией в C++. Для указателей на методы наоборот — стандартной конверсией является приведние типа вниз по иерархии. Эти конверсии могут выполняться неявно.

Тем не менее в С++ при помощи 'static_cast' можно сделать приведения и в обратном направлении. В С++ можно сделать так:

class A {};
class B : public A {};
...
A* pa = new B(); // standard conversion - upcast
B* pb = static_cast<B*>(pa); // static_cast, perfectly legal, downcast


С указателями на методы ситуация соврешенно аналогична. Чтобы сделать приведение типа такого указателя вверх по иерархии нало применить 'static_cast'.

class A { public: void foo(); };
class B : public A { public: int bar(int); };

void (B::*p1)() = &A::foo; // standard conversion
int (A::*p2)(int) = static_cast<int (A::*)(int)>(&B::bar); // static_cast, perfectly legal


Ты говоришь, что такие приведения типов нелегальны? Неверно. Они совершенно легальны (если соблюдены требования стандарта).

Ты говоришь, что такие приведения типов опасны? Да, они могут быть опасны, если ими неправильно пользоваться. Операция деления тоже опасна — можно случайно что-нибудь на 0 разделить.

Ты говоришь, что это тоже самое, что модификация константной строки? Неверно. Модификация константной строки безусловно приводит к underfined behavior. А при таком приведении underfined behavior возникнет, только если нарушены условия, делающие такое приведение безопасным. В наших примерах эти условия пока соблюдались (как и в примере, на который отвечал Стив Клэмидж).

АТ>>В переводе на язык С++ "b указывает на подобъект B класса C" означает, что статический тип объекта '*b' равен B, а динамический тип объекта '*b' равен C. У класса С есть член, на который указывет 'pf', поэтому все требования стандарта тут строго соблюдены.


PI>У тебя проблемы с переводом не только с английского языка: это верно в случае с lvalue. т.е.

PI>B* b; C c; *b = c;
PI>В случае с rvalue (наш случай) "The dynamic type of an rvalue expression is its static type."

Ха ха ха! Ты разбираешься в данном вопросе даже меньше, чем я предполагал. Где ты в нашем случае увидел rvalue? Мы работаем с указателями. Запомни раз и навсегда: если 'b' — это указатель, то '*b' — это lvalue. Из этого правила нет исключений. Это и есть наш случай.

Прочитай определение dynamic type, там как раз наш пример и приведен

1.3.3. dynamic type
...
[Example: if a pointer 'p' whose static type is "pointer to class B" is pointing to an object of class D, derived from B, the dynamic type of the expression '*p' is D.]

АТ>>Если у тебя этот пример "не работает", это означает, что твой компилятор глючит.

PI>Это значит, что ты не понимаешь: почему он "не работает".

Детский сад...

PI>>> Все работает верно и в случае с
B* b = static_cast<B*>((void*)&TestC)
.


АТ>>Вот это и есть пример кода, который ни в коем случае нельзя использовать в своих программах. В таком случае, если что-то и "работает", то по чисто случайным причинам. Уже указывалось, что если поменять порядок следования баз класса С, то он перестанет "работать". Это во-первых. А во-вторых, стандарт С++ вообше не гарантирует, что хотя бы одна из баз класса имеет тот же адрес, что и сам экземпляр класса. Этот код дает undefined behavior.


PI>Этот код работает, потому что b начинает указывать на начало обекта TestC, не на подобъект B класса С. И, разумеется, не важен порядок следования базовых классов.


Нет, это код начинает работать потому, что в данном случае глюк компилятора компенсируется глюком программиста. А в обшем случае он не работает. Попробуй на GCC или просто вставь в исходник для MSVC++ 6 вот такое

#pragma pointers_to_members(full_generality, multiple_inheritance)

И посмотри, какой пример будет работать, а какой нет. Узнаешь для себя много нового. Потом будешь говорить, кто что понимает и кто чего не понимает.
Best regards,
Андрей Тарасевич
Re[10]: Множественное наследование: разминка для мозгов
От: IT Россия linq2db.com
Дата: 16.02.02 02:02
Оценка: -1
Здравствуйте Alex Fedotov, Вы писали:

AF>Андрей, пожалуйста, не надо переносить методы ведения дискуссии R.Z1 сюда. Спасибо.


Честно говоря, я бы лучше порекомендовал поучится дискутировать Павлу. Хотя, судя по его 18 постингам, большая половина из которых явно с понтами, это бесполезно. В общем, ещё одно юное дарование.

ЗЫ. Павел, я конечно понимаю, что Вы (можно мне на 'ВЫ') балшой специалист по чтению книжек и стандартов, и маленький в веб-дизайне, но не могли бы Вы подправить фоновую картинку на своей домашней страничке. Это совсем не трудно. А то выглядит это на моём древнем домашнем мониторчике весьма отстойно.

ЗЫ2. Андрей, take it easy, не стоит так болезненно реагировать на провокационные выпады вчерашних студентов. И уж тем более обкладывать их нулями. Много чести.
Если нам не помогут, то мы тоже никого не пощадим.
Re[11]: Множественное наследование: разминка для костей
От: Максим Эстония http://mem.ee
Дата: 16.02.02 09:50
Оценка:
Здравствуйте Андрей Тарасевич, Вы писали:

AF>>Андрей, пожалуйста, не надо переносить методы ведения дискуссии R.Z1 сюда. Спасибо.


АТ>В этой дискуссии, надо сказать, вышеупомянутые методы явственно проступили несколько раньше, чем я их сюда принес...


Предлагаю переименовать тему из "разминка для мозгов" в "разминка для костей" или же "промывка мозгов".
Have fun: Win+M, Ctrl+A, Enter
Re[12]: Множественное наследование: разминка для костей
От: Кодт Россия  
Дата: 16.02.02 14:53
Оценка:
Здравствуйте Андрей Тарасевич:

М>Предлагаю переименовать тему из "разминка для мозгов" в "разминка для костей" или же "промывка мозгов". :shuffle:


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

Так сказать, четко и по-военному.

Мне, к примеру, неочевидно, что возможны преобразования C -> B -> C и CFunc -> BFunc -> CFunc (с чего все началось). Продемонстрируйте, что ли, а то все на пальцАх...

(кстати, раз уж начал бередить прошлое)
получается, что танковый (thunk) код все же должен где-то быть:
  • в начале метода (проверка и приведение this к родному классу) — но это чертовски непродуктивно для статических вызовов
  • в месте вызова (pB->*pfnB)(); — а откуда понятно, что надо приводить именно к C, а не еще дальше (см. ниже)
  • в месте взятия ссылкок на метод: pfnB = (PFNB)fnC; и на объект: pB = &c;

    class A { int m_a; public: A(): m_a(1) {} };
    
    class B { int m_b; public: B(): m_b(2) {} };
    
    class C: public A, public B
    {
    int m_c;
    public:
      C(): m_c(3) {}
      void fnc(void) { assert(m_c == 3); };
    };
    
    class D { int m_d; public: D(): m_d(4) {} };
    
    class E: public E, public C { int m_e; public: E(): m_e(5) {} };
    
    typedef void (B::*PFNB)(void);
    
    E e;
    
    B* pb = &e; // pb == { here=(void*)&e + sizeof(D) + sizeof(A); origin=&e }
    
    PFNB pfnb = (PFNB)(C::fnc);
    
    (pb->*pfnb)(); // как вот здесь pb будет приведен именно к C, а не к E ?
  • Перекуём баги на фичи!
    Re[13]: Множественное наследование: разминка для костей
    От: Андрей Тарасевич Беларусь  
    Дата: 16.02.02 18:45
    Оценка: 30 (2)
    Здравствуйте Кодт, Вы писали:

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


    К>Так сказать, четко и по-военному.


    Для MSVC++ не забываем вставить

    #pragma pointers_to_members(full_generality, multiple_inheritance)


    или поменять соответствующую установку компиляции.

    К>class A { int m_a; public: A(): m_a(1) {} };
    
    К>class B { int m_b; public: B(): m_b(2) {} };
    
    К>class C: public A, public B
    К>{
    К>int m_c;
    К>public:
    К>  C(): m_c(3) {}
    К>  void fnc(void) { assert(m_c == 3); };
    К>};
    
    К>class D { int m_d; public: D(): m_d(4) {} };
    
    К>class E: public E, public C { int m_e; public: E(): m_e(5) {} };


    Имелось в виду, как я понимаю,

    class E: public D, public C ...


    К>typedef void (B::*PFNB)(void);
    
    К>E e;
    
    К>B* pb = &e; // pb == { here=(void*)&e + sizeof(D) + sizeof(A); origin=&e }
    
    Здесь: 'pb' указывает на базовый подобъект типа 'B' в составе объекта 'е'.
    
    К>PFNB pfnb = (PFNB)(C::fnc);


    Правильно так:

    PFNB pfnb = (PFNB)(&C::fnc);


    Здесь образуется указатель на метод класса. Этот указатель в MSVC++ состоит из двух 32-хбитных полей: старшая часть — смещение this (будем обозначать словом 'offset'), младшая часть — собственно точка входа в метод (будем обозначать словом 'entry'). Рассмотрим создание этого указателя по шагам.

    Сначала вычисляется '&C::fnc', т.е. создается собственно указатель типа 'void (C::*)()'. Это указатель будет иметь следующий вид

    [entry = &C::fnc, offset = 0]


    Затем выполняется приведение типа от 'void (C::*)()' к 'void (B::*)()'. Это приведение типа для такого указателя выполняется по следующему правилу: поле 'entry' остается таким как было, а из поля 'offset' вычитается смещение класса B (результирующий тип) в классе C (исходный тип). Это смещение в данном случае равно 4. Т.е. в результате приведения типа получается такой указатель

    [entry = &C::fnc, offset = -4]


    Все.

    (pb->*pfnb)(); // как вот здесь pb будет приведен именно к C, а не к E ?


    Вызов через такой указатель делается по следующему алгоритму:

    1. Берется указатель на экземпляр класса и к нему прибавляется поле 'offset' указателя на метод. В нашем случае указатель 'pb' указывает на подобъект типа 'B' в составе объекта типа 'E', а поле 'offset' в 'pfnb' равно -4. Т.е. указатель на объект смещается на 4 байта назад. В результате он будет указывать на подобъект типа 'С' в составе объекта типа 'E'.

    2. Делается обычный вызов метода через поле 'entry' указателя на метод. В качестве значения 'this' этому методу передается значение, вычисленное на шаге 1.

    Все. Как видишь, все правильно работает. Метод 'C::fnc' получает указатель на объект именно типа 'C'.
    Best regards,
    Андрей Тарасевич
    Re[14]: Множественное наследование: разминка для костей
    От: Андрей Тарасевич Беларусь  
    Дата: 16.02.02 19:02
    Оценка:
    АТ>Здравствуйте Кодт, Вы писали:


    АТ>
    (pb->>*pfnb)(); // как вот здесь pb будет приведен именно к C, а не к E ?
    АТ>


    АТ>Вызов через такой указатель делается по следующему алгоритму:


    АТ>1. Берется указатель на экземпляр класса и к нему прибавляется поле 'offset' указателя на метод. В нашем случае указатель 'pb' указывает на подобъект типа 'B' в составе объекта типа 'E', а поле 'offset' в 'pfnb' равно -4. Т.е. указатель на объект смещается на 4 байта назад. В результате он будет указывать на подобъект типа 'С' в составе объекта типа 'E'.


    АТ>2. Делается обычный вызов метода через поле 'entry' указателя на метод. В качестве значения 'this' этому методу передается значение, вычисленное на шаге 1.


    Это алгоритм для даного конкретного случая. А в общем случае, при вызове метода через указатель 'px' типа 'void (X::*)()' с указателем на объект 'py' типа 'Y*'

    (py->*px)();


    делаются следующие шаги

    1. Делается приведение типа указателя 'py' к типу 'X*'. При этом указатель на объект может сместиться.

    2. К указателю, полученному на шаге 1, прибавляется поле 'offset' указателя 'px'. При этом указатель на объект может еще сместиться.

    3. Делается обычный вызов метода через поле 'entry' указателя 'px'. В качестве значения 'this' этому методу передается значение, вычисленное на шаге 2.

    В нашем примере шага 1 не было, потому что типы X и Y совпадали.
    Best regards,
    Андрей Тарасевич
    Re: Множественное наследование: разминка для мозгов
    От: Roman Fadeyev  
    Дата: 18.02.02 07:24
    Оценка:
    Спасибо всем за участие. Я не ожидал такого энтузиазма.

    Особое спасибо Андрею Тарасевичу и Pavel Ivlev за глубокие познания в области спецификации и истории С++. Хотя иногда эти исторические очерки и принимали слегка "разгоряченный" вид.(Кстати, кто это так усердно ставил Pavel Ivlev нули? )

    Я рад, что поднятая мною тема оказалась полезна.


    Всем спасибо
    Re[2]: Множественное наследование: разминка для мозгов
    От: Кодт Россия  
    Дата: 18.02.02 07:31
    Оценка:
    Здравствуйте Roman Fadeyev, Вы писали:

    RF>Спасибо всем за участие. Я не ожидал такого энтузиазма.


    RF>Особое спасибо Андрею Тарасевичу и Pavel Ivlev за глубокие познания в области спецификации и истории С++. Хотя иногда эти исторические очерки и принимали слегка "разгоряченный" вид.(Кстати, кто это так усердно ставил Pavel Ivlev нули? )


    RF>Я рад, что поднятая мною тема оказалась полезна.


    RF>Всем спасибо


    Ну что, прямая дорога в QnA.
    Перекуём баги на фичи!
    Re[2]: Множественное наследование: разминка для мозгов
    От: Андрей Тарасевич Беларусь  
    Дата: 18.02.02 08:28
    Оценка:
    Здравствуйте Roman Fadeyev, Вы писали:

    RF>Спасибо всем за участие. Я не ожидал такого энтузиазма.


    RF>Особое спасибо Андрею Тарасевичу и Pavel Ivlev за глубокие познания в области спецификации и истории С++. Хотя иногда эти исторические очерки и принимали слегка "разгоряченный" вид.


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

    RF>(Кстати, кто это так усердно ставил Pavel Ivlev нули? )


    Нули ставил я. (Если ты откроешь интересующее тебя сообщение и нажмеш на оценку в заголовке, ты увидишь, кто конкретно ставил оценки). Нули я поставил именно на те сообщения Павла, которые содержать не соответствующую действительности информацию.
    Best regards,
    Андрей Тарасевич
    Re[3]: Множественное наследование: разминка для мозгов
    От: Roman Fadeyev  
    Дата: 18.02.02 10:12
    Оценка:
    Здравствуйте Андрей Тарасевич, Вы писали:

    АТ>Учитывая тот факт, что я и Павел в течение данной дискусии придерживались противоположных точек зрения, такое общее "спасибо" мне кажется несколько ...э-э-э.... сбивающим с толку


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

    АТ>Нули ставил я. (Если ты откроешь интересующее тебя сообщение и нажмеш на оценку в заголовке, ты увидишь, кто конкретно ставил оценки).


    Я знаю, это шутка. Видели там подмигивающую рожу? Вот такую: . Вот и чудненько, а теперь — по пивку.
    Re[2]: Множественное наследование: разминка для мозгов
    От: Pavel Ivlev www.vsi.ru/~pavel
    Дата: 18.02.02 11:30
    Оценка:
    Здравствуйте Roman Fadeyev, Вы писали:

    RF>Спасибо всем за участие. Я не ожидал такого энтузиазма.


    RF>Особое спасибо Андрею Тарасевичу и Pavel Ivlev за глубокие познания в области спецификации и истории С++. Хотя иногда эти исторические очерки и принимали слегка "разгоряченный" вид.(Кстати, кто это так усердно ставил Pavel Ivlev нули? ;) )


    RF>Я рад, что поднятая мною тема оказалась полезна.


    Не спешите меня хоронить.
    Re[10]: Множественное наследование: разминка для мозгов
    От: Pavel Ivlev www.vsi.ru/~pavel
    Дата: 18.02.02 12:03
    Оценка:
    Здравствуйте Андрей Тарасевич, Вы писали:

    AТ>Нет. Попытка изменения константной строки приводит к неопределенному поведению. Это действительно not allowed. А приведние типа, о котром мы говорим, не только разрешено, но еше и четко определено (при соблюдении ряда условий).


    Нотация const_cast<T>(e) призвана заменить (T)e для преобразований, которым нужно получить доступ к данным с модификатором const или volatile.

    АТ>Ты говоришь, что такие приведения типов нелегальны? Неверно. Они совершенно легальны (если соблюдены требования стандарта).


    Я говорю?? Кажется мы говорили о правилах определяющих такие приведения.

    АТ>Ты говоришь, что такие приведения типов опасны? Да, они могут быть опасны, если ими неправильно пользоваться. Операция деления тоже опасна — можно случайно что-нибудь на 0 разделить.


    АТ>Ты говоришь, что это тоже самое, что модификация константной строки? Неверно. Модификация константной строки безусловно приводит к underfined behavior. А при таком приведении underfined behavior возникнет, только если нарушены условия, делающие такое приведение безопасным. В наших примерах эти условия пока соблюдались (как и в примере, на который отвечал Стив Клэмидж).


    Нет, я говорю, что модификация константной строки не допускается компилятором также, как и недопускается компилятором неявное преобразование указателя на член производного класса к члену базового класса.

    АТ>Ха ха ха! Ты разбираешься в данном вопросе даже меньше, чем я предполагал. Где ты в нашем случае увидел rvalue? Мы работаем с указателями. Запомни раз и навсегда: если 'b' — это указатель, то '*b' — это lvalue. Из этого правила нет исключений. Это и есть наш случай.


    Поясню (параграф 6, раздел 5.5):
    If the result of .* or ->* is a function, then that result can be used only as the operand for the function call operator (). [Example:
    (ptr_to_obj>*ptr_to_mfct)(10); calls the member function denoted by ptr_to_mfct for the object pointed to by ptr_to_obj. ] The
    result of a .* expression is an lvalue only if its first operand is an lvalue and its second operand is a pointer to data member. The result of an ->* expression is an lvalue only if its second operand is a pointer to data member. If the second operand is the null pointer to member value (4.11), the behavior is undefined.


    В "нашем случае" я имел ввиду то, что мы имеем дело как раз с не "data member". К тому же, код:
    struct A{
        int a;
    };
    struct B{
        int b;
    };
    
    struct C:public A, public B
    {
        int c;
    };
    
    int main()
    {
        C TestC;    
        TestC.a=1; TestC.b=2; TestC.c=3; 
        B* pB=&TestC;
        int B::* pmb = static_cast<int B::*>(&C::c);
        cout << endl << pB->*pmb << endl; 
        return 0;
    }

    Работает так, как надо.

    АТ>Прочитай определение dynamic type, там как раз наш пример и приведен


    АТ>1.3.3. dynamic type

    АТ>...
    АТ>[Example: if a pointer 'p' whose static type is "pointer to class B" is pointing to an object of class D, derived from B, the dynamic type of the expression '*p' is D.]

    Повторяю: "The dynamic type of an rvalue expression is its static type."

    АТ>>>Если у тебя этот пример "не работает", это означает, что твой компилятор глючит.

    PI>>Это значит, что ты не понимаешь: почему он "не работает".

    АТ>Детский сад... :)))


    Не каждый день приходится общаться с интелектуалами. Извини.

    PI>>>> Все работает верно и в случае с
    B* b = static_cast<B*>((void*)&TestC)
    .


    АТ>>>Вот это и есть пример кода, который ни в коем случае нельзя использовать в своих программах. В таком случае, если что-то и "работает", то по чисто случайным причинам. Уже указывалось, что если поменять порядок следования баз класса С, то он перестанет "работать". Это во-первых. А во-вторых, стандарт С++ вообше не гарантирует, что хотя бы одна из баз класса имеет тот же адрес, что и сам экземпляр класса. Этот код дает undefined behavior.


    Согласен.

    АТ>Попробуй на GCC или просто вставь в исходник для MSVC++ 6 вот такое


    А что, GCC — это свод законов? Можно не отвечать. Просто посмотри что написано там: http://www.geocrawler.com/archives/3/361/1998/12/0/2002500/

    Как видно, GCC (хоть и в меньшей степени, чем MSVC) тоже не лишен глюков.

    АТ>#pragma pointers_to_members(full_generality, multiple_inheritance)


    Не люблю вещи, зависящие от реализации. Надежней добавить член "virtual void Test() = 0;" в класс B.
    Re[11]: Множественное наследование: разминка для мозгов
    От: Pavel Ivlev www.vsi.ru/~pavel
    Дата: 18.02.02 12:11
    Оценка:
    Здравствуйте Андрей Тарасевич, Вы писали:

    АТ>Здравствуйте Pavel Ivlev, Вы писали:


    AT>>>Возьмем первый draft стандарта С++ (28 апреля 1995) и посмотрим на соответствующий пункт там:


    АТ>>>5.2.8 Static cast

    АТ>>>[...]
    АТ>>>9 An rvalue of type "pointer to member of D of type cv1 T" can be converted to an rvalue of type "pointer to member of B of type cv2 T", where B is a base class of D, if a valid standard conversion from "pointer to member of B of type cv2 T" to "pointer to member of D of type cv2 T" exists, and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. [...] If class B contains or inherits the original member, the resulting pointer to member points to the member in class B. Otherwise, the result of the cast is undefined.

    АТ>>>(Сравни выдеделенные строки с соотв. строками финальной версии стандарта или даже второго драфта)


    PI>>Я незнаю: с чем ты это сравниваешь, но у меня стандарт от 1998-09-01, и там написано:

    PI>>"An rvalue of type “pointer to member of D of type cv1 T” can be converted to an rvalue of type “pointer to
    PI>>member of B of type cv2 T”, where B is a base class (10) of D, if a valid standard conversion from “pointer
    PI>>to member of B of type T” to “pointer to member of D of type T” exists (4.11), and cv2 is the same cvqualification
    PI>>as, or greater cvqualification
    PI>>than, cv1.59) The null member pointer value (4.11) is converted
    PI>>to the null member pointer value of the destination type. If class B contains the original member, or is a
    PI>>base or derived class of the class containing the original member, the resulting pointer to member points to
    PI>>the original member. Otherwise, the result of the cast is undefined.
    [Note: although class B need not contain
    PI>>the original member, the dynamic type of the object on which the pointer to member is dereferenced
    PI>>must contain the original member; see 5.5. ]"

    PI>>По-твоему семантика различна?


    Да, написать-написал, а до конца не прочитал. Виноват.
    Re[11]: Множественное наследование: разминка для мозгов
    От: Pavel Ivlev www.vsi.ru/~pavel
    Дата: 18.02.02 12:13
    Оценка:
    Здравствуйте IT, Вы писали:

    IT>А то выглядит это на моём древнем домашнем мониторчике весьма отстойно.


    Если не поможет смена монитора, воспользуйтесь Opera.
    Re[12]: Множественное наследование: разминка для мозгов
    От: IT Россия linq2db.com
    Дата: 18.02.02 15:32
    Оценка:
    Здравствуйте Pavel Ivlev, Вы писали:

    PI>Если не поможет смена монитора, воспользуйтесь Opera.


    Смена монитора не помогла, на работе на разрешении 1152x864 тот же эффект, могу прислать скриншот. А Оперой и NN я проверяю сразу, это у меня уже становится профпривычкой :)
    Если нам не помогут, то мы тоже никого не пощадим.
    Re[13]: Множественное наследование: разминка для мозгов
    От: Pavel Ivlev www.vsi.ru/~pavel
    Дата: 18.02.02 15:56
    Оценка:
    Здравствуйте IT, Вы писали:

    IT>Смена монитора не помогла, на работе на разрешении 1152x864 тот же эффект, могу прислать скриншот. А Оперой и NN я проверяю сразу, это у меня уже становится профпривычкой :)


    Ну я же ни на что не претендую.
    Но когда я хочу автоматически вставить тэг [xxx] у Вас на форуме, то под Opera (6.01) это сделать не удается. Так что у кого-то рыльце в пушку:)
    Re[14]: Множественное наследование: разминка для мозгов
    От: IT Россия linq2db.com
    Дата: 18.02.02 16:05
    Оценка:
    Здравствуйте Pavel Ivlev, Вы писали:

    PI>Но когда я хочу автоматически вставить тэг [xxx] у Вас на форуме, то под Opera (6.01) это сделать не удается. Так что у кого-то рыльце в пушку


    Не может быть, должно работать.
    Если нам не помогут, то мы тоже никого не пощадим.
    Re[15]: Множественное наследование: разминка для мозгов
    От: Pavel Ivlev www.vsi.ru/~pavel
    Дата: 18.02.02 16:23
    Оценка:
    Здравствуйте IT, Вы писали:

    IT>Не может быть, должно работать.


    Я обманывать буду что ли. В тот раз вообще неработало. Сейчас если текст выделяешь и хочешь заключить в теги, то под IE все нормально, а под Opera снова не работает.
    Re[11]: Множественное наследование: разминка для мозгов
    От: Андрей Тарасевич Беларусь  
    Дата: 18.02.02 17:20
    Оценка:
    Здравствуйте Pavel Ivlev, Вы писали:

    AТ>>Нет. Попытка изменения константной строки приводит к неопределенному поведению. Это действительно not allowed. А приведние типа, о котром мы говорим, не только разрешено, но еше и четко определено (при соблюдении ряда условий).


    PI>Нотация const_cast<T>(e) призвана заменить (T)e для преобразований, которым нужно получить доступ к данным с модификатором const или volatile.


    Что ты хочешь этим сказать? Какой именно "доступ" ты имеешь в виду? Доступ "по чтению" к данным с модификатором 'const' имеется и так. Доступ "по записи" к данным с модификатором 'const' запрещен — он всегда приводит к неопределенному поведению. И 'const_cast' тут ничего поменять не в силах.

    Оператор приведения типа 'const_cast' предназначен для того, чтобы убрать модификатор 'const' c пути доступа к данным (т.е. с указателя на эти даные или со ссылки на эти данные), а не с самих данных. Если сами данные являются константными, то попытка модификации их через такой указатель или ссылку все равно будет приводить к неопределенному поведению.

    void copy(char* dst, char* src) { strcpy(dst, src); }
    
    char sz1[] = "hello";
    const char sz2[] = "world";
    char sz3[100];
    
    const char* lpsz1 = sz1;
    const char* lpsz2 = sz2;
    
    strcpy(sz3, const_cast<char*>(lpsz1)); // OK, данные неконстантны
    strcpy(sz3, const_cast<char*>(lpsz2)); // OK, данные константны, но нет попытки модификации
    
    const_cast<char*>(lpsz1)[3] = 'A'; // OK, данные неконстантны
    const_cast<char*>(lpsz2)[3] = 'A'; // ОШИБКА!!! Попытка модификации константных данных



    АТ>>Ты говоришь, что такие приведения типов нелегальны? Неверно. Они совершенно легальны (если соблюдены требования стандарта).


    PI>Я говорю?? Кажется мы говорили о правилах определяющих такие приведения.


    Насколько я помню, ты выступал за совершенно безапеляционное "This is not allowed" и точка. Ни о каких правилах ты и слышать не хотел.

    АТ>>Ты говоришь, что такие приведения типов опасны? Да, они могут быть опасны, если ими неправильно пользоваться. Операция деления тоже опасна — можно случайно что-нибудь на 0 разделить.


    АТ>>Ты говоришь, что это тоже самое, что модификация константной строки? Неверно. Модификация константной строки безусловно приводит к underfined behavior. А при таком приведении underfined behavior возникнет, только если нарушены условия, делающие такое приведение безопасным. В наших примерах эти условия пока соблюдались (как и в примере, на который отвечал Стив Клэмидж).


    PI>Нет, я говорю, что модификация константной строки не допускается компилятором также, как и недопускается компилятором неявное преобразование указателя на член производного класса к члену базового класса.


    Неявное не допускается. Об этом спору нет. О том, что неявное не допускается я сказал еще в самом первом ответе тебе. Но мы говорим не о неявном. Мы говорим о явном применении 'static_cast'.

    О константных строках: компилятор не в состоянии распознать моификацию константной стрки, сделанную через 'const_cast'. Тем не менее это не допускается.

    АТ> Где ты в нашем случае увидел rvalue? Мы работаем с указателями. Запомни раз и навсегда: если 'b' — это указатель, то '*b' — это lvalue. Из этого правила нет исключений. Это и есть наш случай.


    PI>Поясню (параграф 6, раздел 5.5):

    PI>
    PI>If the result of .* or ->* is a function, then that result can be used only as the operand for the function call operator (). [Example:
    (ptr_to_obj>>*ptr_to_mfct)(10); calls the member function denoted by ptr_to_mfct for the object pointed to by ptr_to_obj. ] The
    PI>result of a .* expression is an lvalue only if its first operand is an lvalue and its second operand is a pointer to data member. The result of an ->* expression is an lvalue only if its second operand is a pointer to data member. If the second operand is the null pointer to member value (4.11), the behavior is undefined.
    PI>


    PI>В "нашем случае" я имел ввиду то, что мы имеем дело как раз с не "data member".


    Здесь говорится о результате вычисления операторов ->* и .* Это к нашему вопросу вообще никак не относится. В нашем случае понятие dynamic type всплыло потому, что в стандарте сказано, что для правильной разадресации указателя на член класса, динамический тип объекта, указуемого 'ptr_to_obj', должен содержать этот член класса. Об этом говорилось в разделе про 'static_cast', который тут уже не раз цитировался, в качестве замечания. Об этом говорится в 5.5/4 явно:

    4 If the dynamic type of the object does not contain the member to which the pointer refers, the behavior is undefined.

    АТ>>Прочитай определение dynamic type, там как раз наш пример и приведен


    АТ>>1.3.3. dynamic type

    АТ>>...
    АТ>>[Example: if a pointer 'p' whose static type is "pointer to class B" is pointing to an object of class D, derived from B, the dynamic type of the expression '*p' is D.]

    PI>Повторяю: "The dynamic type of an rvalue expression is its static type."


    Ты смотришь не на тот expression.

    PI>>>>> Все работает верно и в случае с
    B* b = static_cast<B*>((void*)&TestC)
    .


    АТ>>>>Вот это и есть пример кода, который ни в коем случае нельзя использовать в своих программах. В таком случае, если что-то и "работает", то по чисто случайным причинам. Уже указывалось, что если поменять порядок следования баз класса С, то он перестанет "работать". Это во-первых. А во-вторых, стандарт С++ вообше не гарантирует, что хотя бы одна из баз класса имеет тот же адрес, что и сам экземпляр класса. Этот код дает undefined behavior.


    PI>Согласен.


    АТ>>Попробуй на GCC или просто вставь в исходник для MSVC++ 6 вот такое


    PI>А что, GCC — это свод законов? Можно не отвечать. Просто посмотри что написано там: http://www.geocrawler.com/archives/3/361/1998/12/0/2002500/


    Я не привожу GCC в качестве свода законов. Просто тут кто-то даже утверждал, что такой код не может работать, потому что компилятор "никак не может определить правильное значени this в такой ситуации". Вот я и привел пример компилятора, который каким то "магическим" образом определяет првильно значение this а такой ситуации.

    PI>Как видно, GCC (хоть и в меньшей степени, чем MSVC) тоже не лишен глюков.


    В данном случае мы наблюдаем именно безглючное поведение.

    АТ>>#pragma pointers_to_members(full_generality, multiple_inheritance)


    PI>Не люблю вещи, зависящие от реализации.


    Но от установок компиляции никуда не денешься. Они есть в любом компиляторе. В данном случае MSVC++ работает неправильно только потому, что кто-то выставил некорректные установки компиляции. Эта прагма делает их корректными. Вот и все. Вместо прагмы можно залезть в project settings и сделать исправления там.

    PI>Надежней добавить член "virtual void Test() = 0;" в класс B.


    Если дизайн программы не требует полиморфных классов, а они там появляются вот по таким причинам — это очень криво.
    Best regards,
    Андрей Тарасевич
    Re[12]: Множественное наследование: разминка для мозгов
    От: Аноним  
    Дата: 18.02.02 18:34
    Оценка:
    Здравствуйте Андрей Тарасевич, Вы писали:

    АТ>Оператор приведения типа 'const_cast' предназначен для того, чтобы убрать модификатор 'const' c пути доступа к данным (т.е. с указателя на эти даные или со ссылки на эти данные), а не с самих данных. Если сами данные являются константными, то попытка модификации их через такой указатель или ссылку все равно будет приводить к неопределенному поведению.


    Да, именно это я и хотел сказать.

    АТ>Насколько я помню, ты выступал за совершенно безапеляционное "This is not allowed" и точка. Ни о каких правилах ты и слышать не хотел.


    Не так. Я наставивал на "This is not allowed" и ссылался на "Note" (из 5.2.9), поскольку полагал, что мы имеем дело с rvalue (6, раздел 5.5), а для rvalue динамический тип — это его статический тип (в нашем случае B, а у B члена TestC нет). А с "data member" (6, раздел 5.5) я у MSVC проблем не заметил.

    АТ>Я не привожу GCC в качестве свода законов. Просто тут кто-то даже утверждал, что такой код не может работать, потому что компилятор "никак не может определить правильное значени this в такой ситуации". Вот я и привел пример компилятора, который каким то "магическим" образом определяет првильно значение this а такой ситуации.


    Ну, в общем, понятно, что у MSVC в данном случае проблемы с thiscall (при компиляции по-умолчанию).
    Re[12]: Множественное наследование: разминка для мозгов
    От: Pavel Ivlev www.vsi.ru/~pavel
    Дата: 18.02.02 18:37
    Оценка:
    АТ>Оператор приведения типа 'const_cast' предназначен для того, чтобы убрать модификатор 'const' c пути доступа к данным (т.е. с указателя на эти даные или со ссылки на эти данные), а не с самих данных. Если сами данные являются константными, то попытка модификации их через такой указатель или ссылку все равно будет приводить к неопределенному поведению.

    Да, именно это я и хотел сказать.

    АТ>Насколько я помню, ты выступал за совершенно безапеляционное "This is not allowed" и точка. Ни о каких правилах ты и слышать не хотел.


    Не так. Я наставивал на "This is not allowed" и ссылался на "Note" (из 5.2.9), поскольку полагал, что мы имеем дело с rvalue (6, раздел 5.5), а для rvalue динамический тип — это его статический тип (в нашем случае B, а у B члена TestC нет). А с "data member" (6, раздел 5.5) я у MSVC проблем не заметил.

    АТ>Я не привожу GCC в качестве свода законов. Просто тут кто-то даже утверждал, что такой код не может работать, потому что компилятор "никак не может определить правильное значени this в такой ситуации". Вот я и привел пример компилятора, который каким то "магическим" образом определяет првильно значение this а такой ситуации.


    Ну, в общем, понятно, что у MSVC в данном случае проблемы с thiscall (при компиляции по-умолчанию).

    P.S.
    А нельзя сделать так, чтобы при отправке сообщения с пустым полем "Имя" появлялось предупреждение.
    Re[14]: Множественное наследование: разминка для костей
    От: PoM-PoM 40mm Россия  
    Дата: 31.10.03 10:12
    Оценка:
    АТ>
    К>>class A { int m_a; public: A(): m_a(1) {} };
    
    К>>class B { int m_b; public: B(): m_b(2) {} };
    
    К>>class C: public A, public B
    К>>{
    К>>int m_c;
    К>>public:
    К>>  C(): m_c(3) {}
    К>>  void fnc(void) { assert(m_c == 3); };
    К>>};
    
    К>>class D { int m_d; public: D(): m_d(4) {} };
    
    К>>class E: public D, public C { int m_e; public: E(): m_e(5) {} };
    АТ>



    АТ>
    АТ>PFNB pfnb = (PFNB)(&C::fnc);
    АТ>



    АТ>Затем выполняется приведение типа от 'void (C::*)()' к 'void (B::*)()'. Это приведение типа для такого указателя выполняется по следующему правилу: поле 'entry' остается таким как было, а из поля 'offset' вычитается смещение класса B (результирующий тип) в классе C (исходный тип). Это смещение в данном случае равно 4. Т.е. в результате приведения типа получается такой указатель


    простите, но мне кажется что приведение указателей на члены класса работает в обратную сторону -- это 'void (B::*)()' можно привести к 'void (C::*)()' а не наоборот! Ведь 'void (C::*)()' может указывать на метод которого у B просто нет, а потому проверит возможность вызова на этапе компиляции невозможно
    Will give me piece of mind
    Re[15]: Множественное наследование: разминка для костей
    От: Андрей Тарасевич Беларусь  
    Дата: 31.10.03 10:22
    Оценка:
    Здравствуйте, PoM-PoM 40mm, Вы писали:

    АТ>>Затем выполняется приведение типа от 'void (C::*)()' к 'void (B::*)()'. Это приведение типа для такого указателя выполняется по следующему правилу: поле 'entry' остается таким как было, а из поля 'offset' вычитается смещение класса B (результирующий тип) в классе C (исходный тип). Это смещение в данном случае равно 4. Т.е. в результате приведения типа получается такой указатель


    PP4>простите, но мне кажется что приведение указателей на члены класса работает в обратную сторону -- это 'void (B::*)()' можно привести к 'void (C::*)()' а не наоборот! Ведь 'void (C::*)()' может указывать на метод которого у B просто нет, а потому проверит возможность вызова на этапе компиляции невозможно


    В С++ много чего проверить на этапе компиляции невозможно. Читай, в общем, либо эту дискуссию, либо, посвежее, здесь

    http://www.rsdn.ru/Forum/Message.aspx?mid=427474&amp;only=1
    Автор: Андрей Тарасевич
    Дата: 30.10.03
    Best regards,
    Андрей Тарасевич
    Re[15]: Множественное наследование: разминка для костей
    От: Аноним  
    Дата: 31.10.03 11:42
    Оценка:
    Здравствуйте, PoM-PoM 40mm, Вы писали:



    АТ>>
    К>>>class A { int m_a; public: A(): m_a(1) {} };
    
    К>>>class B { int m_b; public: B(): m_b(2) {} };
    
    К>>>class C: public A, public B
    К>>>{
    К>>>int m_c;
    К>>>public:
    К>>>  C(): m_c(3) {}
    К>>>  void fnc(void) { assert(m_c == 3); };
    К>>>};
    
    К>>>class D { int m_d; public: D(): m_d(4) {} };
    
    К>>>class E: public D, public C { int m_e; public: E(): m_e(5) {} };
    АТ>>



    АТ>>
    АТ>>PFNB pfnb = (PFNB)(&C::fnc);
    АТ>>



    АТ>>Затем выполняется приведение типа от 'void (C::*)()' к 'void (B::*)()'. Это приведение типа для такого указателя выполняется по следующему правилу: поле 'entry' остается таким как было, а из поля 'offset' вычитается смещение класса B (результирующий тип) в классе C (исходный тип). Это смещение в данном случае равно 4. Т.е. в результате приведения типа получается такой указатель


    PP4>простите, но мне кажется что приведение указателей на члены класса работает в обратную сторону -- это 'void (B::*)()' можно привести к 'void (C::*)()' а не наоборот! Ведь 'void (C::*)()' может указывать на метод которого у B просто нет, а потому проверит возможность вызова на этапе компиляции невозможно


    Да, выглядит криво, примерно как
    C c;
    B *pb = &c;
    pb->C::func();
    Re: Множественное наследование: разминка для мозгов
    От: e-Xecutor Россия  
    Дата: 31.10.03 12:02
    Оценка:
    Здравствуйте, Roman Fadeyev, Вы писали:

    [skip]
    Не ссорьтесь горячие финские парни.
    Да, на первый взгляд тут косяк.
    Но таки Андрей Тарасевич прав.
    Тот код который тут "неправильный" на самом деле правильный, а тот который обозван правильным на самом деле неправильный
    Прикол в том, что если всегда использовать правильные pmf, будет определённый penalty на производительность,
    а так как фитча эта юзается весьма редко, а если и юзается то только для интерфейсов, то по умолчанию pmf это обычный указатель.
    Поэтому для msvc и bcc32 нужно явно сказать что "я суперкрутой перец, я шарю в multiple inheritance".
    У bcc32 это опция -Vmp (хотя казалось бы должо быть -Vmm , кажется они поменяны местами.), у cl это -vmm (или прагма).
    g++, насколько я знаю, всегда генерирует wide pointers для pmf.
    Re: Множественное наследование: разминка для мозгов
    От: dad  
    Дата: 07.11.03 08:55
    Оценка:
    RF>Пораскинув мозгами, поведение компилятора можно понять: мы просили указатель на базовый класс B, он нам его и дал, слегка сместив. Во втором случае имеем передачу адреса (void*), и компилятор послушно ее выполняет.

    RF>Ответа я, собственно, не прошу. Просто хочу показать фишку. Ни в одном учебнике по С++, что я читал, такого примера не встречал, так что может кому сберегу лоб (см грабли)


    RF>Если у кого есть похожие байки, или он хочет чё-нить выразить по поводу сабжа, или хочет назвать меня ослом, буду рад выслушать



    Ответ один — "Потому что так программировать нельзя"
    Веру-ю-у! В авиацию, в научную революци-ю-у, в механизацию сельского хозяйства, в космос и невесомость! Веру-ю-у! Ибо это объективно-о! (Шукшин)
    Re[14]: Множественное наследование: разминка для костей
    От: adontz Грузия http://adontz.wordpress.com/
    Дата: 08.11.03 00:28
    Оценка:
    Здравствуйте, Андрей Тарасевич, Вы писали:

    Здравствуйте, Андрей Тарасевич, Вы писали:

    АТ>
    АТ>#pragma pointers_to_members(full_generality, multiple_inheritance)
    АТ>


    Во первых как я понял ты пыташься сделать указатель на метод 8 байтовым (4+4) и за счёт этого выехать, это так?
    Во вторых, в плоскости не стандарта, а MS VC который оказываеться умеет. Я вот английский не очень плохо знаю, а так и не понял причём данная прагма и почему именно она решает проблему.

    C/C++ Preprocessor Reference

    pointers_to_membersSee Also
    Pragma Directives
    C++ Specific

    #pragma pointers_to_members( pointer-declaration, [most-general-representation] )
    Specifies whether a pointer to a class member can be declared before its associated class definition and is used to control the pointer size and the code required to interpret the pointer. You can place a pointers_to_members pragma in your source file as an alternative to using the /vmx compiler options or the inheritance keywords.

    The pointer-declaration argument specifies whether you have declared a pointer to a member before or after the associated function definition. The pointer-declaration argument is one of the following two symbols:

    Argument Comments
    full_generality Generates safe, sometimes nonoptimal code. You use full_generality if any pointer to a member is declared before the associated class definition. This argument always uses the pointer representation specified by the most-general-representation argument. Equivalent to /vmg.
    best_case Generates safe, optimal code using best-case representation for all pointers to members. Requires defining the class before declaring a pointer to a member of the class. The default is best_case.

    The most-general-representation argument specifies the smallest pointer representation that the compiler can safely use to reference any pointer to a member of a class in a translation unit. The argument can be one of the following:

    Argument Comments
    single_inheritance The most general representation is single-inheritance, pointer to a member function. Causes an error if the inheritance model of a class definition for which a pointer to a member is declared is ever either multiple or virtual.
    multiple_inheritance The most general representation is multiple-inheritance, pointer to a member function. Causes an error if the inheritance model of a class definition for which a pointer to a member is declared is virtual.
    virtual_inheritance The most general representation is virtual-inheritance, pointer to a member function. Never causes an error. This is the default argument when #pragma pointers_to_members(full_generality) is used.

    Example
    // Specify single-inheritance only
    #pragma pointers_to_members( full_generality, single_inheritance )
    END C++ Specific


    И ещё: 8-байтовый указатель нельзя присвоить 4-хбайтовому так? Если да (скажем можно как-то хитро посчитав их комбинацию) то простые преобразования туда-обратно (а кто мне мешает) всё изгадят и не спасёт никакая прагма. Если нет, то разве это правильно?
    И ещё: с указной прагмой sizeof(pClassB_Func) == 4, так зачем она вообще нужна?
    И ещё: прагма действительно что-то меняет. В зависимости от её состояния правильность вызова медодов меняеться, что именно если знаешь?
    И ещё: ИМХО вне зависимости от правоты в отношении указателей уже высказавшихся ошибка здесь

    class A { public: int a; };
    class B { public: int b; };
    class C: public A, public B
     {
      public:
        void Test() { cout<<"a:"<<a<<"  b:"<<b;}
     };
     
    int main(int argc, char* argv[])
    {
        typedef void (B::*pClassB_Func)();
        C TestC;    //Итак, имеем класс C - наследник A+B
        TestC.a=1;  //Это поле от A
        TestC.b=2;  //Это поле от B
        pClassB_Func pf=static_cast<pClassB_Func>(C::Test); //Указатель на функцию  void C::Test()
    
        //Вот это, казалось бы безобидный вариант. Но неправильный
        B * pB=&TestC;
        (pB->*pf)();
        //Вот это очень ужасный на вид, но РАБОЧИЙ вариант
        pB=static_cast<B*>((void*)&TestC);
        (pB->*pf)();
    
        return 0;
    }


    Разве можно указателю на метод в B присваивать адрес метода в C? Ведь B и всем типам с ним связанным совершенно плевать на то, кто от них унаследован, не так ли? И значит такое приведение не верно, разве можно использовать в базовом классе что-то от производного?
    A journey of a thousand miles must begin with a single step © Lau Tsu
    Re[15]: Множественное наследование: разминка для костей
    От: Андрей Тарасевич Беларусь  
    Дата: 08.11.03 01:12
    Оценка:
    Здравствуйте, adontz, Вы писали:

    АТ>>
    АТ>>#pragma pointers_to_members(full_generality, multiple_inheritance)
    АТ>>


    A>Во первых как я понял ты пыташься сделать указатель на метод 8 байтовым (4+4) и за счёт этого выехать, это так?


    Я пытаюсь заставить MSVC++ 6 вести себя в соответствии со стандартом языка. А каким при этом станет указатель — мне не интересно.

    A>Во вторых, в плоскости не стандарта, а MS VC который оказываеться умеет.


    Что именно "в полоскости стандарта"? "В плоскоти сетандарта" я не должен указывать никаких прагм — все должно работать и так. В вот в MSVC++ 6 без этой прагмы (или без эквивалентной установки проекта) — не работает.

    A>Я вот английский не очень плохо знаю, а так и не понял причём данная прагма и почему именно она решает проблему.


    A>

    A>...
    A>single_inheritance The most general representation is single-inheritance, pointer to a member function. Causes an error if the inheritance model of a class definition for which a pointer to a member is declared is ever either multiple or virtual.
    A>...


    Вот сверху ясно написано, почему умолчательнеая установка (single_inheritance) может привести к ошибке.

    A>И ещё: 8-байтовый указатель нельзя присвоить 4-хбайтовому так? Если да (скажем можно как-то хитро посчитав их комбинацию) то простые преобразования туда-обратно (а кто мне мешает) всё изгадят и не спасёт никакая прагма. Если нет, то разве это правильно?


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

    A>И ещё: с указной прагмой sizeof(pClassB_Func) == 4, так зачем она вообще нужна?


    С какой "указной прагмой"? С '#pragma pointers_to_members(full_generality, multiple_inheritance)
    '? Нет, с ней sizeof(pClassB_Func) == 8. Что касается 4 — это тебя обмынывает дебаггер. Попробуй распечатать sizeof(pClassB_Func) из программы, и ты увидишь, что он равен 8.

    A>И ещё: прагма действительно что-то меняет. В зависимости от её состояния правильность вызова медодов меняеться, что именно если знаешь?


    Ну так в этой ветке, по-моему, ясно написано, что именно меняется. Давай еще раз рассмторим на твоем примере.

    A>И ещё: ИМХО вне зависимости от правоты в отношении указателей уже высказавшихся ошибка здесь


    A>
    A>class A { public: int a; };
    A>class B { public: int b; };
    A>class C: public A, public B
    A> {
    A>  public:
    A>    void Test() { cout<<"a:"<<a<<"  b:"<<b;}
    A> };
     
    A>int main(int argc, char* argv[])
    A>{
    A>    typedef void (B::*pClassB_Func)();
    A>    C TestC;    //Итак, имеем класс C - наследник A+B
    A>    TestC.a=1;  //Это поле от A
    A>    TestC.b=2;  //Это поле от B
    A>    pClassB_Func pf=static_cast<pClassB_Func>(C::Test); //Указатель на функцию  void C::Test()
    A>


    Ты забыл оператор взятия адреса

    pClassB_Func pf = static_cast<pClassB_Func>(&C::Test)


    A>
    A>    //Вот это, казалось бы безобидный вариант. Но неправильный
    A>    B * pB=&TestC;
    A>    (pB->*pf)();
    A>


    Нет, с точки зрения С++ это соврешенно правильный и соврешенно рабочий вариант. Вызывается метод 'C::Test' с указателем 'this' указывающим на начало экземпляра 'TestC'.

    A>
    A>    //Вот это очень ужасный на вид, но РАБОЧИЙ вариант
    A>    pB=static_cast<B*>((void*)&TestC);
    A>    (pB->>*pf)();
    A>


    А вот то уже какая-то ерунда. Работоспособность этого варианта в С++ не гарантируется.

    A> return 0;

    A>}
    A>[/ccode]

    A>Разве можно указателю на метод в B присваивать адрес метода в C?


    Разумеется можно. Такая возможность однозначно гаранируется стандартом языка.

    A>Ведь B и всем типам с ним связанным совершенно плевать на то, кто от них унаследован, не так ли?


    Да, им плевать. Это тем не менее не повод запрещать присваивание.

    A>И значит такое приведение не верно, разве можно использовать в базовом классе что-то от производного?


    А с чего ты взял, что в базовом класса используется что-то от производного? Посмотри сам на свой пример — исползуется экземпляр класса 'C' и вызывается метод класса 'C'. Все в порядке.
    Best regards,
    Андрей Тарасевич
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.