Когда-то я наступил на грабли множественного наследования. А сегодня, вспомнив об этом,
хочу подсунуть их вам, подогреть, так сказать, форум.
Ниже приведены три класса: два базовых и один их наследник
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: Множественное наследование: разминка для мозгов
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: Множественное наследование: разминка для мозгов
Здравствуйте 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, Вы писали:
Ant>Здравствуйте Roman Fadeyev, Вы писали:
Ant>Кстати обрати внимание. Если изменить порядок базовых классов в наследнике, то все работает как надо в обоих вариантах.
Ant>class C:public B,public A Ant>{ Ant>public:
Ant>... Ant>};
Ant>
Ну понятно, в чем и прикол. Но чтоб твой вариант не работал, достаточно вместо B* использовать указатель A*
Re[3]: Множественное наследование: разминка для мозгов
Здравствуйте pdx, Вы писали:
pdx>А такие вещи в учебниках не пишут. Тут нужно что-либо посерьезнее. Для примера, "C++ for Real Programmers" Элджера. Там все это хорошо прописано. И вообще книга полезная. Не для практического применения , а для расширения мировоззрения .
А ты не знаешь, может ее где качнуть можно?
Do not fake yourself ;) ICQ#: 198114726
Re: Множественное наследование: разминка для мозгов
Здравствуйте Roman Fadeyev, Вы писали:
RF>Когда-то я наступил на грабли множественного наследования. А сегодня, вспомнив об этом, RF>хочу подсунуть их вам, подогреть, так сказать, форум.
В GCC этого эффекта не будет. А вообще, это тут уже пробегало неделю назад.
Здравствуйте pdx, Вы писали:
pdx>А такие вещи в учебниках не пишут. Тут нужно что-либо посерьезнее. Для примера, "C++ for Real Programmers" Элджера. Там все это хорошо прописано. И вообще книга полезная. Не для практического применения , а для расширения мировоззрения .
Такие вещи в учебниках не пишут потому, что это баг компилятора MSVC++ 6. С чего бы это о нем стали писать в учебниках?
Best regards,
Андрей Тарасевич
Re[4]: Множественное наследование: разминка для мозгов
Здравствуйте Кодт, Вы писали:
К>Примерчик-то — гремучий:
Примерчик совершенно корректный.
К>1) преобразовали &C::Test к void(*B::pfn)(void) К>(а почему не к LPTHREADPROC, например?)
Потому что спеуцификация С++ не может предсказать поведение программы, которое получится после преобразования и вызова через LPTHREADPROC. А преобразование к 'void(*B::pfn)()' — совершенно легально и должно давать правильные резулитаты при таком способе вызова.
К>2) взяли произвольное смещение (приведение типа при множественном наследовании)
А приведение типа при множественном наследовании не берет произвольное смещение. Оно берет правильное смещение.
Best regards,
Андрей Тарасевич
Re: Множественное наследование: разминка для мозгов
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]: Множественное наследование: разминка для мозгов
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> должен рожать шлюзовую функцию
Здравствуйте 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]: Множественное наследование: разминка для мозгов
Здравствуйте Кодт, Вы писали:
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 при вызове метода. Поменяй установки проекта, как сказано здаесь
Здравствуйте Андрей Тарасевич, Вы писали:
RF>>>>>Пораскинув мозгами, поведение компилятора можно понять: мы просили указатель на базовый класс B, он нам его и дал, слегка сместив.
АТ>>>После этого компилятор должен был заметить, что для правильного вызова метода через указатель 'pf' надо еще раз "слегка сместить" указатель 'pB'. И все дожно работать правльно. Но MSVC++ 6 этого не заметил.
К>>А с какой это радости он должен "смещать обратно"?
АТ>Компилятор должен "смещать обратно" по той простой причине, что это требуется стандартом языка. А как он это делает — это уже его проблема.
К>>Или static_cast<PFN> должен рожать шлюзовую функцию
К>>на которую передавать управление?
АТ>Нет, это делается проще. Тут уже говорилось об этом не раз. В составе указателя на метод, кроме самого указателя на точку входа в метод, хранится еще смещение класса B в составе класса С, котрое во всех таких ситуация позволяет вычислить правильное значение this при вызове метода. Поменяй установки проекта, как сказано здаесь
АТ>http://www.rsdn.ru/forum/message.asp?mid=28006&only
АТ>и все будет прекрасно вызываться без всяких шлюзовых функций.
Даа... интересно.
Может, заодно объясните (или подскажете — где глянуть), как этот сдвоенный указатель выглядит и как с ним работают?
Кстати, насколько я понимаю, такой цирк не прокатит с 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&only
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]: Множественное наследование: разминка для мозгов
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Здравствуйте Roman Fadeyev, Вы писали:
RF>>Когда-то я наступил на грабли множественного наследования. А сегодня, вспомнив об этом, RF>>хочу подсунуть их вам, подогреть, так сказать, форум.
АТ>В GCC этого эффекта не будет. А вообще, это тут уже пробегало неделю назад.
АТ>http://www.rsdn.ru/forum/message.asp?mid=24570&only
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]: Множественное наследование: разминка для мозгов
Здравствуйте Pavel Ivlev, Вы писали:
RF>>>Когда-то я наступил на грабли множественного наследования. А сегодня, вспомнив об этом, RF>>>хочу подсунуть их вам, подогреть, так сказать, форум.
АТ>>В GCC этого эффекта не будет. А вообще, это тут уже пробегало неделю назад.
АТ>>http://www.rsdn.ru/forum/message.asp?mid=24570&only
А>Выдержки из "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'). Код надо исправить так:
Да, Steve Clamage прав в том, что такие конверсии опасны тем, что при вызове метода через такой указатель, компилятор не всегда имеет возможность проверить, что данный экземпляр класса A является частью класса B. Если вдруг окажется, что это не так, то такой вызов приведет к катастрофе. Тем не менее стандарт С++ такую конверсию не запрещает. За правильностью вызовов при этом предлагается следить программисту. По крайней мере в примере кода, присутствовавшем в постановке вопроса, делалась совершенно безопасная инициализация указателя. Ни о каком "it isn't allowed" здесь речи быть не может.
Best regards,
Андрей Тарасевич
Re[4]: Множественное наследование: разминка для мозгов
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>В своем ответе 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]: Множественное наследование: разминка для мозгов
Здравствуйте 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 годом
когда завершенного стандарта у языка С++ еще не было.
Как мы уже убедились, в стандарт языка Стиву Клэмиджу (образца 1995 года :) ) все таки стоит заглянуть. По крайней мере в финальную его версию. Точнее, я уверен, что кто-кто, а Стив Клэмидж прекрасно ориентируется в финальной версии стандарта С++. А вот твои представления о С++, мягко говоря, обросли мхом.