Здравствуйте Roman Fadeyev, Вы писали:
RF>Спасибо всем за участие. Я не ожидал такого энтузиазма.
RF>Особое спасибо Андрею Тарасевичу и Pavel Ivlev за глубокие познания в области спецификации и истории С++. Хотя иногда эти исторические очерки и принимали слегка "разгоряченный" вид.(Кстати, кто это так усердно ставил Pavel Ivlev нули? ;) )
RF>Я рад, что поднятая мною тема оказалась полезна.
Не спешите меня хоронить.
Re[10]: Множественное наследование: разминка для мозгов
Здравствуйте Андрей Тарасевич, Вы писали:
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 вот такое
Здравствуйте Андрей Тарасевич, Вы писали:
АТ>Здравствуйте 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, Вы писали:
PI>Если не поможет смена монитора, воспользуйтесь Opera.
Смена монитора не помогла, на работе на разрешении 1152x864 тот же эффект, могу прислать скриншот. А Оперой и NN я проверяю сразу, это у меня уже становится профпривычкой :)
Если нам не помогут, то мы тоже никого не пощадим.
Re[13]: Множественное наследование: разминка для мозгов
Здравствуйте IT, Вы писали:
IT>Смена монитора не помогла, на работе на разрешении 1152x864 тот же эффект, могу прислать скриншот. А Оперой и NN я проверяю сразу, это у меня уже становится профпривычкой :)
Ну я же ни на что не претендую.
Но когда я хочу автоматически вставить тэг [xxx] у Вас на форуме, то под Opera (6.01) это сделать не удается. Так что у кого-то рыльце в пушку:)
Re[14]: Множественное наследование: разминка для мозгов
Здравствуйте Pavel Ivlev, Вы писали:
PI>Но когда я хочу автоматически вставить тэг [xxx] у Вас на форуме, то под Opera (6.01) это сделать не удается. Так что у кого-то рыльце в пушку
Не может быть, должно работать.
Если нам не помогут, то мы тоже никого не пощадим.
Re[15]: Множественное наследование: разминка для мозгов
Здравствуйте IT, Вы писали:
IT>Не может быть, должно работать.
Я обманывать буду что ли. В тот раз вообще неработало. Сейчас если текст выделяешь и хочешь заключить в теги, то под IE все нормально, а под Opera снова не работает.
Re[11]: Множественное наследование: разминка для мозгов
Здравствуйте 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]: Множественное наследование: разминка для мозгов
АТ>Оператор приведения типа '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]: Множественное наследование: разминка для костей
К>>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]: Множественное наследование: разминка для костей
Здравствуйте, PoM-PoM 40mm, Вы писали:
АТ>>Затем выполняется приведение типа от 'void (C::*)()' к 'void (B::*)()'. Это приведение типа для такого указателя выполняется по следующему правилу: поле 'entry' остается таким как было, а из поля 'offset' вычитается смещение класса B (результирующий тип) в классе C (исходный тип). Это смещение в данном случае равно 4. Т.е. в результате приведения типа получается такой указатель
PP4>простите, но мне кажется что приведение указателей на члены класса работает в обратную сторону -- это 'void (B::*)()' можно привести к 'void (C::*)()' а не наоборот! Ведь 'void (C::*)()' может указывать на метод которого у B просто нет, а потому проверит возможность вызова на этапе компиляции невозможно
В С++ много чего проверить на этапе компиляции невозможно. Читай, в общем, либо эту дискуссию, либо, посвежее, здесь
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: Множественное наследование: разминка для мозгов
[skip]
Не ссорьтесь горячие финские парни.
Да, на первый взгляд тут косяк.
Но таки Андрей Тарасевич прав.
Тот код который тут "неправильный" на самом деле правильный, а тот который обозван правильным на самом деле неправильный
Прикол в том, что если всегда использовать правильные pmf, будет определённый penalty на производительность,
а так как фитча эта юзается весьма редко, а если и юзается то только для интерфейсов, то по умолчанию pmf это обычный указатель.
Поэтому для msvc и bcc32 нужно явно сказать что "я суперкрутой перец, я шарю в multiple inheritance".
У bcc32 это опция -Vmp (хотя казалось бы должо быть -Vmm , кажется они поменяны местами.), у cl это -vmm (или прагма).
g++, насколько я знаю, всегда генерирует wide pointers для pmf.
Re: Множественное наследование: разминка для мозгов
RF>Пораскинув мозгами, поведение компилятора можно понять: мы просили указатель на базовый класс B, он нам его и дал, слегка сместив. Во втором случае имеем передачу адреса (void*), и компилятор послушно ее выполняет.
RF>Ответа я, собственно, не прошу. Просто хочу показать фишку. Ни в одном учебнике по С++, что я читал, такого примера не встречал, так что может кому сберегу лоб (см грабли)
RF>Если у кого есть похожие байки, или он хочет чё-нить выразить по поводу сабжа, или хочет назвать меня ослом, буду рад выслушать
Ответ один — "Потому что так программировать нельзя"
Веру-ю-у! В авиацию, в научную революци-ю-у, в механизацию сельского хозяйства, в космос и невесомость! Веру-ю-у! Ибо это объективно-о! (Шукшин)
Re[14]: Множественное наследование: разминка для костей
Во первых как я понял ты пыташься сделать указатель на метод 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; //Это поле от BpClassB_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>Во первых как я понял ты пыташься сделать указатель на метод 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>
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'. Все в порядке.