Здравствуйте, Irokez, Вы писали:
I>Вопрос вообщем в следующем.
[skip]
I>вообщем привести указатель на функцию к типу void*. I>Компилятор ругается матом. Всякие x_cast<> — тож не помогают. I>Есть ли какое нибудь решенеие.
указатель на функцию-член нельзя приводить к void*
меняй дизайн
зачем тебе это нужно?
I>Кстати прочитал Александреску — ничего примечательного
резко ты тему переменил...
ну и правильно, он ламер ваще.
да вот решил замахнуться на милкософтовский РПС. мутит меня как они там всего навыдумывали.
а надо эт для того чтобы некий гипотетический программист создал класс ну к примеру так
на сервере
class A {
public:
virtual void __stdcall F1(int k)
{
printf("A::F1(%d)\n",k);
}
};
и где-то там-же
A a;
MyRpcRegisterMethod("A::F1",&a,A::F1);
на стороне клиента
MyRpcCall("A::F1",5);
ну и соответственно на сервере
вывелось бы
A::F1(5)
По поводу Александреску. Ничего он там куртого (как в предисловии от Майерса написано) не придумал. Просто обощил. И то в непонятной форме. Элджер и то лучше изложил.
А его реализация Functor это вообще полный ужас. Типа если вам надо вызвать функцию с более 15 парметрами исправте код в моей библиотеке
Hello, Irokez!
You wrote on Tue, 21 Dec 2004 22:09:16 GMT:
I> да вот решил замахнуться на милкософтовский РПС. мутит меня как они там всего навыдумывали. I> а надо эт для того чтобы некий гипотетический программист создал класс ну к примеру так
[тут был код]
Ну пять балов, чудо маршалинг, и как в МС не догадались до такого элегантного решения, чтобы в рантайме парсить строку для вызова метода?
I> По поводу Александреску. Ничего он там куртого (как в предисловии от Майерса написано) не I> придумал. Просто обощил. И то в непонятной форме. Элджер и то лучше изложил.
Мдя...
I> А его реализация Functor это вообще полный ужас. Типа если вам надо вызвать функцию с более 15 I> парметрами исправте код в моей библиотеке
Ну я в шоке лично, не знаю как остальная публика. Скажи пожалуйста уважаемый, а как ты хочешь маршалить функции без писанины:
Насчет уважаемого эт хорошо. Но спасибо я уже разобрался с этим без всяких там шаблонов. Ребята если вас научили мыслить стереотипно, то это не значит что все другие должны мылить как авторы книг которые вы читаете. (Прошу прощения ничего личного.)
Отвечу одно без всяких проблем могу получить адресс функции метода класса и привести его к любому типу. Если кому интересно могу написать статью в RSDN. А ваши издевки можете оставить при себе.
Hello, Irokez!
You wrote on Tue, 21 Dec 2004 22:48:39 GMT:
I> Насчет уважаемого эт хорошо. Но спасибо я уже разобрался с этим без всяких там шаблонов. Ребята I> если вас научили мыслить стереотипно, то это не значит что все другие должны мылить как авторы I> книг которые вы читаете. (Прошу прощения ничего личного.)
Да ладно, всё нормально, но см. ниже.
I> Отвечу одно без всяких проблем могу получить адресс функции метода класса и привести его к I> любому типу.
Можно на этом по подробнее?
I> Если кому интересно могу написать статью в RSDN. А ваши издевки можете оставить при I> себе.
Напишите по подробнее ваш код, без шаблонов, и чтобы всё это было бы переносимо... Мне действительно интересно.
Posted via RSDN NNTP Server 1.9 delta
"Man feed machine
Machine feed man"
Peter Gabriel — OVO — The Tower That Ate People
Здравствуйте, Irokez, Вы писали:
I>Спорю на пиво что возможно. Я уже код набросал работет на ура
Это тебе показалось, что твой код работает. Просто ты его плохо и однобоко тестировал, если вообще тестировал.
"Я уже код набросал работет на ура" — это стандартный этап в эволюции любителя приводить указатели на методы классов к типу 'void*' (как тебе уже сказали, тут этот вопрос возникает по нескольку раз на неделе). Это нормально, но рекомендуется не застревать на этом этапе надолго.
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Здравствуйте, Irokez, Вы писали:
I>>Спорю на пиво что возможно. Я уже код набросал работет на ура
АТ>Это тебе показалось, что твой код работает. Просто ты его плохо и однобоко тестировал, если вообще тестировал.
АТ>"Я уже код набросал работет на ура" — это стандартный этап в эволюции любителя приводить указатели на методы классов к типу 'void*' (как тебе уже сказали, тут этот вопрос возникает по нескольку раз на неделе). Это нормально, но рекомендуется не застревать на этом этапе надолго.
Батенька если вас здесь оценили как эксперт то это не значит что ВЫ на самом деле им являетесь. А как ВЫ варазились любитель я уже 14 лет занимаюсь только С и С++.
Hello, Irokez!
You wrote on Tue, 21 Dec 2004 23:00:13 GMT:
I> Батенька если вас здесь оценили как эксперт то это не значит что ВЫ на самом деле им являетесь. I> А как ВЫ варазились любитель я уже 14 лет занимаюсь только С и С++.
Можно код увидеть? Хоть намётки, а то не усну. если не увижу! Не уж то всех нас (а я то же сейчас занимаюсь маршалингом) переплюнули?
Posted via RSDN NNTP Server 1.9 delta
"Man feed machine
Machine feed man"
Peter Gabriel — OVO — The Tower That Ate People
Hello, Андрей!
You wrote on Tue, 21 Dec 2004 22:56:37 GMT:
АТ> любителя
Кстати, вот это слово несколько обидно. Большинство же не любители, а "новички", учащиеся так сказать. А все мы такие были и остаемся, так как с C++ можно каждый день открывать что-то новое
Posted via RSDN NNTP Server 1.9 delta
"Man feed machine
Machine feed man"
Peter Gabriel — OVO — The Tower That Ate People
Вообщем я так и понял что найдутся возомнившие о себе крутые папики, которые будут учить "любителей" как правильно программировать. Обиделся я.
Для тех кто в танке Получить адресс функции метода класса одназначно можно, без всяких хаков. Стандартным синтакисом С++. Проверяно в VC7.1 код совместим работает как, в релиз так, и в дебаг версии с уникодом и без.
Здравствуйте, Irokez, Вы писали:
I>Батенька если вас здесь оценили как эксперт то это не значит что ВЫ на самом деле им являетесь. А как ВЫ варазились любитель я уже 14 лет занимаюсь только С и С++.
В программировании, как и в математике митинговать смысла нету.
Код в студию и все встанет на свои места.
А особенность указателя на метод класса в том,
что он не может существовать без объекта.
Вот хоть ты тресни, но нужна пара (объект, метод)
в этом то и причина невозможности преобразования к void*
Тут дело даже не в физическом представлении а в логическом.
Невозможно вызвать метод объекта не имея самого объекта.
В любом случае удачи!
Re[2]: Указатель на функцию
От:
Аноним
Дата:
21.12.04 23:17
Оценка:
Здравствуйте, Irokez, Вы писали:
I>Вообщем я так и понял что найдутся возомнившие о себе крутые папики, которые будут учить "любителей" как правильно программировать. Обиделся я.
I>Для тех кто в танке Получить адресс функции метода класса одназначно можно, без всяких хаков. Стандартным синтакисом С++. Проверяно в VC7.1 код совместим работает как, в релиз так, и в дебаг версии с уникодом и без.
Дак ты о темы то не уходи.
То что можно взять адрес метода класса, с этим никто не спорит.
Вопрос о том как его преобразовать в void*
Hello, Irokez!
You wrote on Tue, 21 Dec 2004 23:13:10 GMT:
I> Для тех кто в танке Получить адресс функции метода класса одназначно можно, без всяких хаков. I> Стандартным синтакисом С++. Проверяно в VC7.1 код совместим работает как, в релиз так, и в дебаг I> версии с уникодом и без.
Можно всё же код.
Posted via RSDN NNTP Server 1.9 delta
"Man feed machine
Machine feed man"
Peter Gabriel — OVO — The Tower That Ate People
Смешно вы говорите.
Трасировал на встроенном дизасемблере вызов функции метода класса. Или я тут один такой умный (не примите как возвышение над вами). неявный указатель this, нужен только для одной вещи, чтобы обратиться к член-данным класса, и ни при чем тут функции.
Здравствуйте, Irokez, Вы писали:
АТ>>"Я уже код набросал работет на ура" — это стандартный этап в эволюции любителя приводить указатели на методы классов к типу 'void*' (как тебе уже сказали, тут этот вопрос возникает по нескольку раз на неделе). Это нормально, но рекомендуется не застревать на этом этапе надолго. I>Батенька если вас здесь оценили как эксперт то это не значит что ВЫ на самом деле им являетесь.
А это соврешенно не важно. Для того, чтобы знать таблицу умножения не надо быть профессором математики. Для того, чтобы разбитаться в таких базовых вопросах, как корректность приведения типов в С++, не надо быть экспертом С++.
I>А как ВЫ варазились любитель я уже 14 лет занимаюсь только С и С++.
У меня есть определенные трудности с точным парсингом этого предложения из-за отсутсвия в нем знаков препинания (зрителям: "казнить нельзя помиловать", хе-хе-хе). Но общий смысл мне понятен.
Ну как вам сказать... Представьте себе бабушку, которая 60 лет водила машину по маршруту дом-рыбный магазин где-нибудь в вечнозеленой Калифорнии. Внимание, вопрос: годятся ли ее 60 лет водительского стажа для езды по московской гололедице?
Вот так же и у вас: 14 лет вы занимались культивацией у себя в голове кривых неправильных представлений о С и С++. Это, извините, совсем плохо.
Best regards,
Андрей Тарасевич
Re[7]: Указатель на функцию
От:
Аноним
Дата:
21.12.04 23:24
Оценка:
Здравствуйте, Irokez, Вы писали:
I>Здравствуйте, Аноним, Вы писали:
I>Смешно вы говорите. I>Трасировал на встроенном дизасемблере вызов функции метода класса. Или я тут один такой умный (не примите как возвышение над вами). неявный указатель this, нужен только для одной вещи, чтобы обратиться к член-данным класса, и ни при чем тут функции.
Слова все это. Слова.
Код где?
Там кода должно быть с гулькин нос
Hello, Irokez!
You wrote on Tue, 21 Dec 2004 23:20:23 GMT:
I> Смешно вы говорите. I> Трасировал на встроенном дизасемблере вызов функции метода класса. Или я тут один такой умный I> (не примите как возвышение над вами). неявный указатель this, нужен только для одной вещи, чтобы I> обратиться к член-данным класса, и ни при чем тут функции. КОД В СТУДИЮ
Ессесно корректного приведения указателя на метод класса к void*, и без хаков.
Posted via RSDN NNTP Server 1.9 delta
"Man feed machine
Machine feed man"
Peter Gabriel — OVO — The Tower That Ate People
К сожалению Я уже написал что тема закрыта. А о том как привести тип спросите у ЭКСПЕРТА, MICROSOFT, или додумайтесь сами.
Кстати что поразительно у меня есть 100% рабочий код приведения метода класса к любому типу, а вторым постом после моего идет ответ что это сделать невозможно, а не вопрос как это сделать. Как говорили у нас в академии учите мат. часть.
Ребята я не гений, и я не кичусь своими знаниями, я не писал приложения Хэлло Ворлд ни разу.
Здравствуйте, Irokez, Вы писали:
I>К сожалению Я уже написал что тема закрыта. А о том как привести тип спросите у ЭКСПЕРТА, MICROSOFT, или додумайтесь сами. I>Ребята я не гений, и я не кичусь своими знаниями, я не писал приложения Хэлло Ворлд ни разу
Неожидал что за ночь тут развернётся такая жаркая дискуссия
Честно говоря удивлён как на ровном месте создалась конфликтная ситуация.
Не понимаю зачем закрывать тему?
Вот демагогию заканчивать надо, а обсуждение технических вопросов предлагаю продолжить.
Вдруг вы заблуждаетесь или мы вас не поняли.
I>Кстати что поразительно у меня есть 100% рабочий код приведения метода класса к любому типу, а вторым постом после моего идет ответ что это сделать невозможно
ну тут всё ясно
простой указатель и указатель на функцию-член суть разные вещи, поэтому в лоб это преобразование сделать невозможно. Возможно это приведение будет работать на какой то платформе в простейшем случае, но в общем случае это сделать нельзя.
Но можно сделать например так:
обернуть указатель на функцию-член в структуру и указатель на её экземпляр уже привести к
void*.
О чем спор? Прежде чем дискутировать, как меня учили, следует опредеиться в терминологии.
Если разобраться, то окажется, что позиция Irokeza отличается от позиции ALL в двух вопросах.
1. Что такое адресс функции-члена?
Irokez — это тот физ. адрес, по которму делается call при вызове функции на уровне машинного кода.
ALL — это семейство типов С++, в соответствии с синтаксисом которого, подставляя адрес объекта и аргументы, получается корректный вызов функции (с учетом виртуальности и множественного / виртуального наследованя)
2. Что таке привдение типов?
Irokez — это насильственное приведение, когда исходное значение и результирующее совпадют только численно (и то не всегда)
ALL — это один из принципов С++, гарнатирующий корректное поведение программы с точки зрения компилятора.
Так вот, исходя из этих терминов, обе стороны по-своему правы .
Это, конечно, мое ИМХО, возможно, я чет недопонял.
Здравствуйте, Irokez, Вы писали:
I>Здравствуйте, Went, Вы писали:
I>Полностью поддерживаю высказываение.
I>Но, с точки зрения синтаксиса в моем коде я не выхожу за пределы стандартных С++ преобразований.
class A {
public:
void __stdcall F1(int k)
{
printf("A::F1(%d)\n",k);
}
};
и есть второй класс
class B: public A {
public:
void __stdcall F1(int k,int d)
{
printf("B::F1(%d,%d)\n",k,d);
}
};
теперь внимание ответ.
struct Method {
void* mthdOwner;
void* mthdFunction;
Method(void* Owner,...)
{
va_list mthd;
va_start(mthd,Owner);
mthdOwner = Owner;
mthdFunction = va_arg(mthd,void*);
va_end(mthd);
}
};
Ну и где то в коде
B b;
Method m(&b,B::F1);
И теперь пусть хоть кто-то скажет что нельзя привести указатель на функцию к типу войд*, или другому типу
Re[2]: Указатель на функцию
От:
Аноним
Дата:
22.12.04 08:50
Оценка:
Идея понятна.
Создается промежуточный объект, который хранит пару (объект, адрес метода).
Затем адрес на объект преобразуется в void*
Это в общем стандратное и нормальное решение.
Правда реализовать это можно куда элегантнее и красивее
и в итоге получишь некий аналог известного из boost'а
Но это ведь не то же самое, о чем говорил Ирокез.
Хотя хрен его знает, о чем он говорил.
Его кода никто пока не видел.
Здравствуйте, А.Якубовский, Вы писали:
АЯ>И теперь пусть хоть кто-то скажет что нельзя привести указатель на функцию к типу войд*, или другому типу
Ну ты представляешь! Все-равно нельзя! Слабо в след. примере вызвать твоим способом A::Func для объекта d?
#include <iostream>
class A
{
int a;
public:
void Func()
{
}
};
class B : virtual public A
{
int b;
};
class C : virtual public A
{
int c;
};
class F
{
int f;
};
class D : public F, public B, public C
{
};
int main()
{
void (A::*func)() = &A::Func;
D d;
(d.*func)();
return 0;
}
Здравствуйте, Glоbus, Вы писали:
G>Здравствуйте, А.Якубовский, Вы писали:
G>Ээээ.. G>1) А как теперь пользотвать этот класс мэтода? G>2) Что случится если sizeof(void*) < sizeof(void A::*) ?
Во первых никогда не встречал чтоб указатель на 32-битной платформе был больше 32
А вам блин еще и подай как маршалинг у меня реализован на основе этого кода. Я блин парился всю ночь, по дружные издевки, а потом дайте мне плз. реализацию маршалинга. Ребята не по взрослому получается.
Здравствуйте, А.Якубовский, Вы писали:
АЯ>Здравствуйте, Аноним, Вы писали: АЯ>Это и есть Ирокез, я просто сменил имя
что скажешь теперь?
class A
{
};
class B: virtual public A {
public:
void __stdcall F1(int k,int d){}
};
int main()
{
B b;
Method m(&b,B::F1);
printf("%i", sizeof (&B::F1));
}
Здравствуйте, А.Якубовский, Вы писали:
АЯ>теперь внимание ответ. АЯ>И теперь пусть хоть кто-то скажет что нельзя привести указатель на функцию к типу войд*, или другому типу
Да вот я скажу, что нельзя. Хочешь? На:
#include <iostream>
using namespace std;
struct A
{
};
struct B : virtual A
{
virtual void foo() {}
};
int main()
{
cout << "size of void* = " << sizeof(void* ) << endl;
cout << "size of &B::foo = " << sizeof(&B::foo) << endl;
}
Здравствуйте, Аноним, Вы писали:
А>Это невозможно.
А>Но boost::bind и boost::function помогут А>тебе решить твою задачу.
А>P.S. Популярный это однако вопрос, о том как указатель на метод А>класса преобразовать в void* . А>Пару раз за неделю он тут возникает
Значит пора Q&A написать... Кто напишет?
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Во-первых, язык С++ не позволяет получть указатель на метод класса без явного использования оператора '&'. Поэтому правильно так
Method m(&b, &B::F1);
(это для любителей изучать язык по ошибкам (и глюкам) компиляторов).
Во-вторых, чтение аргумента типа "указатель на метод класса" через 'va_arg(..., void*)' является банальным хаком, ибо приводит к неопределенному поведению (это к вопросу о приведении типов "без хака").
Во-третих, давай, продемонстрируй нам, как же ты собрался вызывать методы классов через твой "указатель" Я думаю тут никому не составить труда привести тебе хрестоматийный уже пример из пяти-десяти строк, на котором твой "указатель" быстро и громко сядет в лужу
Hello, А.Якубовский!
You wrote on Wed, 22 Dec 2004 08:41:16 GMT:
А>
[Sorry, skipped]
А> теперь внимание ответ.
А> [code]
А> struct Method {
А> void* mthdOwner;
А> void* mthdFunction;
А> Method(void* Owner,...)
А> {
А> va_list mthd;
А> va_start(mthd,Owner);
А> mthdOwner = Owner;
А> mthdFunction = va_arg(mthd,void*);
А> va_end(mthd);
А> }
А> };
А> Ну и где то в коде
А> B b;
А> Method m(&b,B::F1);
А>
А> И теперь пусть хоть кто-то скажет что нельзя привести указатель на А> функцию к типу войд*, или другому типу
Я вам скажу, что Вы облажались Я тоже могу написать
void* tmp = reinterpret_cast<void*>(&A::F1);
, но знаете, как бы я не изгалялся это будет undefined behaviour. То же самое вы и имеете в Вашем случае — вам удалось заставить это работать в частном случае. Надеюсь Вадим Никулин развеял ваши заблуждения.
Здравствуйте, А.Якубовский, Вы писали:
АЯ>Во первых никогда не встречал чтоб указатель на 32-битной платформе был больше 32 АЯ>А вам блин еще и подай как маршалинг у меня реализован на основе этого кода. Я блин парился всю ночь, по дружные издевки, а потом дайте мне плз. реализацию маршалинга. Ребята не по взрослому получается.
И не говори! Совсем не по-взрослому. Это даже не детский сад. И даже не yasli
Ну ничего. Даже после 14 лет практики можно узнать много нового
Кстати. 14 лет назад ты для какой платформы писал? Win16, DOS, MacOS, VAX? Удивительно, что ты не столкнулся с тем фактом, что нельзя приводить указатели на данные и на функции.
PS.
Обычно код, над которым пишут в угаре, — работает только с бодуна.
Здравствуйте, А.Якубовский, Вы писали:
G>>Ээээ.. G>>1) А как теперь пользотвать этот класс мэтода? G>>2) Что случится если sizeof(void*) < sizeof(void A::*) ?
АЯ>Во первых никогда не встречал чтоб указатель на 32-битной платформе был больше 32
Век живи — век учись. На 32-хбитной платформе битовый размер легального указателя на метод класса всегда строго больше 32-х бит. Например, на платформе MSVC++ указатель на метод класса, корректно работающий в условиях множественного наследования имеет размер 64 бита. Это и есть та причина, по котрой вопросы приведения указателя на метод класса к типу 'void*' не заслуживают никакого серъезного рассмотрения.
Не ну вы че издеваетесь. Мне пофиг вопрос был задекларирован. Я дал Вам на него ответ, Функции у меня вызываются на ура, и с виртуальным наследованием и без. Как у вас будут вызываться я не знаю это не мое дело. Читайте тему топика. Можно ли привети указатель на функцию к типу войд*, я не собирался распространяться на тему как использовать это дальше.
Здравствуйте, А.Якубовский, Вы писали:
АЯ>Здравствуйте, Glоbus, Вы писали:
G>>Здравствуйте, А.Якубовский, Вы писали:
G>>Ээээ.. G>>1) А как теперь пользотвать этот класс мэтода? G>>2) Что случится если sizeof(void*) < sizeof(void A::*) ?
АЯ>Во первых никогда не встречал чтоб указатель на 32-битной платформе был больше 32
Указатели разные бывают... Нет никакой гарантии, что указатель на функцию будет иметь тот же размер что и обычный указатель на память. А если ты чего-то не встречал это не значит что его нет.
АЯ>А вам блин еще и подай как маршалинг у меня реализован на основе этого кода. Я блин парился всю ночь, по дружные издевки, а потом дайте мне плз. реализацию маршалинга. Ребята не по взрослому получается.
А мы тут все скопом жестко щас наживетмся на тебе и твоем маршалинге
Ближе к делу. Если уж ты так реально парился, здается мне что парился ты зря, хотя бы потому, что можно одним движением руки избавиться от вопроса sizeof(void*) < sizeof(void A::*)
E>Результат работы под VC++ 7.1: E>sizeof(vp): 4, sizeof(mp): 4
Такой результат ты получишь только при "агрессивных" установках компиляции, при которых указатели на методы классов в MSVC++ в общем случае работают некорректно. При включении "нормальных" установок компиляции размер указателя на метод класса становится равным 16. Есть "промежуточная" установка, при котором размер будет равен 8.
Здравствуйте, А.Якубовский, Вы писали:
АЯ>Здравствуйте, Кодт, Вы писали:
АЯ>Не ну вы че издеваетесь. Мне пофиг вопрос был задекларирован. Я дал Вам на него ответ, Функции у меня вызываются на ура, и с виртуальным наследованием и без. Как у вас будут вызываться я не знаю это не мое дело. Читайте тему топика. Можно ли привети указатель на функцию к типу войд*, я не собирался распространяться на тему как использовать это дальше.
Ты че, друг! Хочешь, чтобы IT-индустрия простаивала из-за тебя целый день, пытаясь доказать, что твое решение не работает? Ты пробовал вызывать ЭТО для классов с виртуальным наследованием?
Читай еще раз здесь
Здравствуйте, А.Якубовский, Вы писали:
АЯ>Здравствуйте, Кодт, Вы писали:
АЯ>Не ну вы че издеваетесь.
Какие издевки.
Не дай бог юзать софт, или еще хуже сопровождать такой
код, где что-то иногда, в очень частных случаях работает.
Я за свои 7 лет опыта (меньше твоего) уже устал
бороться с такими и подобными хаками, хуками и прочими подобными прелестями.
Зачем делать через задницу, когда есть нормальные надежные решения.
Но это уже полный уход от темы.
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Здравствуйте, eao197, Вы писали:
E>>Результат работы под VC++ 7.1: E>>sizeof(vp): 4, sizeof(mp): 4
АТ>Такой результат ты получишь только при "агрессивных" установках компиляции, при которых указатели на методы классов в MSVC++ в общем случае работают некорректно. При включении "нормальных" установок компиляции размер указателя на метод класса становится равным 16. Есть "промежуточная" установка, при котором размер будет равен 8.
Запускал компиляцию так:
cl -GX meth_ptr_size.cpp
Потом просто запустил meth_ptr_size.exe
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, jazzer, Вы писали:
J>Боюсь, что мы сейчас увидим решение с ..., thunks в стиле ATL или еще что-нть непереносимое.
Решение, как видишь, уже опубликовано и включает банальное силовое (и, разумеется, некорректное) приведение указателя на метод к типу 'void*'. Гора родила мышь. Как и следовало ожидать, автор решения наивно полагает, что указатель на метод класса состоит только из адреса точки входа в метод
То г-н Тарасевич, крутой спец по с++, теорик. Никогда не "бравший" в руки отладчик типа Soft Ice или хотя-бы встроенный. Вам как особо одаренному поясню После линковки, в теле программы идет вызов следующего типа. Извините что испоганю асмом ваш любимы С++, но все же
mov ecx, this — я пишу в некотором отступлении от стандартного асма, но что одаренным C++ программистам было понятно
mov eax, dword ptr [ecx+4] — это к примеру вызов виртуального метода +4 это адресс виртуальной функции в таблице vtbl
...
push param1
push param2
push param3
push ecx неявная передача this через стек
call eax
....
Так вот если-б вы зоть раз соизволили сделать трасировку в асме, ВЫ бы подумали прежде чем написать что указатель может быть больше 32
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Век живи — век учись. На 32-хбитной платформе битовый размер легального указателя на метод класса всегда строго больше 32-х бит. Например, на платформе MSVC++ указатель на метод класса, корректно работающий в условиях множественного наследования имеет размер 64 бита. Это и есть та причина, по котрой вопросы приведения указателя на метод класса к типу 'void*' не заслуживают никакого серъезного рассмотрения.
Во! Точно! Я не мог вспомнить, в каких случаях получается 128-битный указатель... Виртуальное наследование + множественное наследование + виртуальные функции. Вуаля!
Здравствуйте, А.Якубовский, Вы писали:
АЯ>Здравствуйте, Кодт, Вы писали:
АЯ>Не ну вы че издеваетесь. Мне пофиг вопрос был задекларирован. Я дал Вам на него ответ, Функции у меня вызываются на ура, и с виртуальным наследованием и без. Как у вас будут вызываться я не знаю это не мое дело. Читайте тему топика. Можно ли привети указатель на функцию к типу войд*, я не собирался распространяться на тему как использовать это дальше.
Держите меня трое И у этого человека 14 лет опыта в Си и Си++
Старик, то что ты сделал это не привдение типа — это не приведение типа. Просто потому что нельзя привести тип void* к типу указателя на функцию. А то что там у тебя — это просто запись в стек sizeof(void B::*) байт и выгребалово sizeof(void*) байтю А куда же остальные sizeof(void B::*) — sizeof(void*) байт деть?
Здравствуйте, А.Якубовский, Вы писали:
АЯ>Не ну вы че издеваетесь. Мне пофиг вопрос был задекларирован. Я дал Вам на него ответ, Функции у меня вызываются на ура, и с виртуальным наследованием и без. Как у вас будут вызываться я не знаю это не мое дело. Читайте тему топика. Можно ли привети указатель на функцию к типу войд*, я не собирался распространяться на тему как использовать это дальше.
Ну ради бога. У тебя компилируется и работает. Только я твои программы на свой компьютер не пущу.
"Неправильный ответ — тоже ответ" (по аналогии с отрицательным результатом).
Hello, А.Якубовский!
You wrote on Wed, 22 Dec 2004 09:09:03 GMT:
А> Не ну вы че издеваетесь. Мне пофиг вопрос был задекларирован. Я дал Вам А> на него ответ, Функции у меня вызываются на ура, и с виртуальным А> наследованием и без. Как у вас будут вызываться я не знаю это не мое А> дело. Читайте тему топика. Можно ли привети указатель на функцию к А> типу войд*, я не собирался распространяться на тему как использовать А> это дальше.
Мне кажеться Вы непоняли — в С++ приводить типы можна какие угодно к каким угодно. Но от этого программа не остается правильной, а неправильные программы могут представлять только академический интерес, уж ни как не промышленный. А зачем нам кони в вакууме? Правильно, незачем. А доказательство в стиле "это у меня работает" никогда не было таковым.
Здравствуйте, А.Якубовский, Вы писали:
АЯ>mov ecx, this — я пишу в некотором отступлении от стандартного асма, но что одаренным C++ программистам было понятно АЯ>mov eax, dword ptr [ecx+4] — это к примеру вызов виртуального метода +4 это адресс виртуальной функции в таблице vtbl АЯ>...
АЯ>push param1 АЯ>push param2 АЯ>push param3 АЯ>push ecx неявная передача this через стек АЯ>call eax
АЯ>....
АЯ>Так вот если-б вы зоть раз соизволили сделать трасировку в асме, ВЫ бы подумали прежде чем написать что указатель может быть больше 32
Душит смех. Нет, молодой человек, если бы Вы хоть раз сделали трассировку правильно откомпилированной программы "в АСМЕ" и поcмотрели, как выглядит вызов метода класса через корректно сформированный указатель на метод класса, Вы бы никогда не стали писать здесь всю эту белиберду и публично садиться в лужу еще и с "асмом".
Специально для Вас я приведу здесь очень показательный пример этого самого "асма" для вызова метода класа через указатель.
Вот С++ код
#include <iostream>
struct A {
int i;
A() : i(1) {}
void print() { std::cout << i << std::endl; }
};
struct B {
int i;
B() : i(2) {}
void print() { std::cout << i << std::endl; }
};
struct C : A, B {
int i;
C() : i(3) {}
};
void call(C* pc, void (C::*pm)()) {
(pc->*pm)();
}
int main() {
C c;
call(&c, &A::print);
call(&c, &B::print);
}
Поясню: обе выделенные жирным инструкции занимаются чтением частей указателя 'pm', который располагается в стеке по адресам esp+4 и esp+8. Советую вам тщательно разобрать этот код и хорошенько подумать о том, что же делают эти две инструкции и почему же это они читают из 32-хбитного (как Вы полагаете) указателя 64 бита информации.
Здравствуйте, А.Якубовский, Вы писали:
АЯ>у меня наследование такое. И все чудно работает
Покажи полный код рабочего примера. Без урывочков...
Кстати, специально для любителей отладки. Вот, соорудил из твоих кусочков:
#include <stdio.h>
#include <stdarg.h>
class A
{
public:
void __stdcall Fn1()
{
printf("A::Fn1()\n");
}
virtual void __stdcall Fn2() = 0;
};
class B : public virtual A
{
public:
void __stdcall Fn1(int& k,int& d)
{
printf("B::Fn1(%d-%d)\n",k,d);
}
virtual void __stdcall Fn2()
{
printf("B::Fn2()\n");
}
};
struct Method
{
Method(void* Owner,...)
{
va_list mthd;
va_start(mthd,Owner);
mthdOwner = Owner;
unsigned long tag1 = va_arg(mthd,unsigned long);
mthdFunction = va_arg(mthd,void*);
unsigned long tag2 = va_arg(mthd,unsigned long);
printf("owner = %p, tag1 = %08X, func = %p, tag2 = %08X\n", mthdOwner, tag1, mthdFunction, tag2);
va_end(mthd);
// dump the stack again...
printf("Ellipsis is ...\n");
va_start(mthd,Owner);
do
{
tag2 = va_arg(mthd,unsigned long);
printf("... %08X\n", tag2);
}
while(tag2 != 0xDEADBEEF);
va_end(mthd);
}
};
int main()
{
B b;
Method m(&b, 0xFEEDFACE, &B::Fn2, 0xDEADBEEF);
}
Вывод:
owner = 0012FF78, tag1 = FEEDFACE, func = 004011A0, tag2 = 00000000 <--- а где же дохлая корова?
Ellipsis is ...
... FEEDFACE
... 004011A0
... 00000000
... 00000004
... DEADBEEF <--- а вот она!
Перекуём баги на фичи!
Re[3]: Указатель на функцию
От:
Аноним
Дата:
22.12.04 10:01
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:
Ну извините всех кого я обидел. Я так понимаю модератор меня забанил. Но еще раз повторюсь без эмоций, вопрос стоял так Можно ли привети указатель к типу войд*. Я специально поставил вопрос таким образом, чтобы можно было понять впринципе возможно ли приведение типов функций. Я для себя ответ нашед можно. Здесь я опубликовал именно ответ на свой вопрос о приведении к типу войд*.
Теперь дабы достичь консенсуа а приведу код который действительно у меня написан.
Обратите пожалуйста внимание на __stdcall. Я готов пообсуждать без эмоций в чем я не прав. Токо пжалуйста не надо стучать себя кулоком в грудь и говорить что тут полный бред.
Еще раз прошу прощения у общественности. Но на самом деле мне было важно не приведение типа к войд*, А приведение указателей на функции с неизвестнвм количеством парметров к некой одному типу, чего я и добился. Я всякие терки по поводу размера адреса функции эт пожалуйста на ваше усмотрение.
Здравствуйте, Андрей Якубовский, Вы писали:
АЯ>Еще раз прошу прощения у общественности. Но на самом деле мне было важно не приведение типа к войд*, А приведение указателей на функции с неизвестнвм количеством парметров к некой одному типу, чего я и добился. Я всякие терки по поводу размера адреса функции эт пожалуйста на ваше усмотрение.
Андрей, заводя новые ники вы еще раз попадаете под несоблюдение правил форума, в частности под спор с модератором. Вам запрещено писать в форум. Извольте соблюдать. В противном случае будет заблокирован ваш IP адрес и отключена возможность читать форум.
Новый ник заблокирован.
Обсуждения — только через moderator@rsdn.ru
Здравствуйте, Аноним, Вы писали:
А>Обратите пожалуйста внимание на __stdcall. Я готов пообсуждать без эмоций в чем я не прав. Токо пжалуйста не надо стучать себя кулоком в грудь и говорить что тут полный бред. А>
Указатели на методы классов в С++ обладают рядом уникальных свойств. На них стандартом языка накладываются определенные требования, удовлетворить которым можно только путем помещения в указатель дополнительной информации, наряду с банальным адресом точки входа в метод. По этой причине, если на некоторой платформа размер адреса равен 32-м битам, то размер корректно реализованного указателя на метод класса всегда заведомо больше 32-х бит (64 бита, например). По этой причине в общем случае невозможно сконвертировать указатель на метод класса в указатель типа 'void*' без потери информации. Какая-то информация будет потеряна. И это приведет к тому, что корректно вызвать метод через такой указатель будет невозможно.
Дополнительная информация, хранимая в указателе на метод, предназначена для вычисления корректного значения указателя 'this' объекта при вызове метода. В условиях обычного одиночного наследования эта информация не нужна. Но вот в условиях множественного наследования (не говоря уже о виртуальном) эта информация играет существенную роль. Я уже приводил пример с множественным наследованием, который демонстрирует (на уровне твоего любимого асма, сгенерированного MSVC++ 6) наличие в указателе на метод класса дополнительной информации (размер указателя в этом примере будет 64 бита) и то, как эта информация используется в процессе вызова. Я уже писал об этом более подробно, можно почитать здесь
Спасибо, что ответели. Все что вы сказали является истинной, я об этом не спорю. Я привел конкретный пример который нужен мне в моей реализации, и который работает на 100%. Возможно в других реализациях он работать не будет, в частности я проверил действительно при перегруженных виртуальных функциях этот код не работает. Но мне это и не надо. А что касаемомо размера функции отмечу что правы как Вы так и я. Вы парвы с точки зрения синтаксиса С++, я прав в постановке вопроса, на который в ответах была приведена куча кода, не отвечающая моим требованиям. Веди изначально вопрос ставился в возможности приведения типов, а не в последующем вызове функции.
Здравствуйте, Аноним, Вы писали:
А>Обратите пожалуйста внимание на __stdcall. Я готов пообсуждать без эмоций в чем я не прав. Токо пжалуйста не надо стучать себя кулоком в грудь и говорить что тут полный бред. А>
Что же касается работоспособности это кода — я не знаю, чем Вы его компилировали, но у меня ни один компилятор не пропускает строчку
void* Fn = mthdFunction;
по очевидным причинам. Предположим, что Ваш компилятор это пропускает. Прекрасно. Тогда могу посоветовать Вам скомпилировать и выполнить вот такой небольшой пример
struct A {
int i;
A() : i(1) {}
void __stdcall print() { std::cout << i << std::endl; }
};
struct B {
int i;
B() : i(2) {}
void __stdcall print() { std::cout << i << std::endl; }
};
struct C : A, B {
int i;
C() : i(3) {}
};
void call(C* pc, void (__stdcall C::*pm)()) {
(pc->*pm)();
Method p(pc, pm);
p.Run(0);
}
int main() {
C c;
call(&c, &A::print);
call(&c, &B::print);
}
и посмотреть на результаты его работы (надеюсь я правильно воспользовался Вашим указателем внутри метода 'call'?). Предлагаю Вам сравнить то, что печатает "стандартный" вызов метода через указатель и то, что печатает вызов через Ваш "указатель".
Я все-таки заставил MSVC++ 6 SP6 компилировать вышеприведенную строчку и вот, к примеру, вывод, который поучается при использовании этого компилятора
1
1
2
1
Как видите, Ваши вызовы всегда печатали '1', в то время как второй "стандартный" вызов напечатал '2', как и должно быть. Надеюсь, это наведет Вас на размышления (все еще надеясь, что я правильно воспользовался Вашим указателем).
Если Вы получаете иные результаты или у Вас имеются какие-то другие вопросы — то, учитывая сложившейся ситуации в форуме, мы можем продолжить эту дискуссию мылом (при условии отсутствия эмоций, разумеется).
Здравствуйте, Аноним, Вы писали:
>>Буду рад услышать Ваш ответ. В чем моя ошибка.
Ошибка в том, что ты выдаешь очень частный случай за верное решение.
И еще и упорствуешь.
Это примерно так же, как и реализовывать функцию сложения двух int
таким вот образом:
int Sum(int a, int b)
{
return 4;
}
В частном случае Sum(2,2); функция работает замечательно и очень эффективно.
Но кому нужно такое решение?
Re[5]: Указатель на функцию
От:
Аноним
Дата:
22.12.04 10:46
Оценка:
Здравствуйте, Андрей Тарасевич, Вы писали:
Это моя оплошность в публикации кода.
вместо
void* Fn = mthdFunction;
надо
Function Fn = mthdFunction;
все компилируется, готов выслать проект, запускается все без проблем.
Здравствуйте, Аноним, Вы писали:
А>Спасибо, что ответели. Все что вы сказали является истинной, я об этом не спорю. Я привел конкретный пример который нужен мне в моей реализации, и который работает на 100%. Возможно в других реализациях он работать не будет, в частности я проверил действительно при перегруженных виртуальных функциях этот код не работает. Но мне это и не надо. А что касаемомо размера функции отмечу что правы как Вы так и я. Вы парвы с точки зрения синтаксиса С++, я прав в постановке вопроса, на который в ответах была приведена куча кода, не отвечающая моим требованиям. Веди изначально вопрос ставился в возможности приведения типов, а не в последующем вызове функции.
А>Буду рад услышать Ваш ответ. В чем моя ошибка.
Для начала стоило бы разобраться с тем, как Вам удалось скомпилировать строчку
void* Fn = mthdFunction;
в Вашем коде. Не один из имеющихся у меня под рукой компиляторов эту строчку в таком виде не пропускает. Вот, например, что сообщает MSVC++ 6 SP 6
error C2440: 'initializing' : cannot convert from 'long (__stdcall Method::*)(void)' to 'void *'
There is no context in which this conversion is possible
Здравствуйте, Аноним, Вы писали:
А>Это моя оплошность в публикации кода.
А>вместо А>void* Fn = mthdFunction; А>надо А>Function Fn = mthdFunction;
А>все компилируется, готов выслать проект, запускается все без проблем.
Да, но в таком случае в Вашем коде вообще не будет приведения указателя на метод класса к типу 'void*'!!! А это, напомню, и было основным камнем преткновения. Зачем же Вы, спрашивается, сообщали, что у Вас в коде прекрасно работает такое приведение, если на самом деле его у Вас в коде нет совсем? Это во-первых.
А во-вторых, Ваш код по прежнему использует только 32 бита указателя на метод — собственно адрес точки входа в метод
...
__asm mov eax,Fn
__asm call eax
...
Этого недостаточно для того, чтобы, например, корректно выполнить приведенный в моем предыдущем сообщении пример. Не затруднит ли Вас попробовать скомпилировать и выполнить этот пример и сообщить сюда, какой-же результат Вы получили?
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>>Век живи — век учись. На 32-хбитной платформе битовый размер легального указателя на метод класса всегда строго больше 32-х бит. Например, на платформе MSVC++ указатель на метод класса, корректно работающий в условиях множественного наследования имеет размер 64 бита. Это и есть та причина, по котрой вопросы приведения указателя на метод класса к типу 'void*' не заслуживают никакого серъезного рассмотрения.
К>Во! Точно! Я не мог вспомнить, в каких случаях получается 128-битный указатель... Виртуальное наследование + множественное наследование + виртуальные функции. Вуаля!
Можно поподробнее?
Что находится в каждых 32 битах? и как осуществляется вызов по этом 128 битному адресу?
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Здравствуйте, jazzer, Вы писали:
J>>Боюсь, что мы сейчас увидим решение с ..., thunks в стиле ATL или еще что-нть непереносимое.
АТ>Решение, как видишь, уже опубликовано и включает банальное силовое (и, разумеется, некорректное) приведение указателя на метод к типу 'void*'. Гора родила мышь. Как и следовало ожидать, автор решения наивно полагает, что указатель на метод класса состоит только из адреса точки входа в метод
да.
я ожидал непереносимости между разными платформами (что-нть в стиле анализа стека, понимания, что же перед нами за указатель, и т.д.), а тут непереносимость уже между по-разному спроектированными классами и по-разному объявленными функциями даже в рамках одной платформы.
Здравствуйте, DangerDen, Вы писали:
К>>Во! Точно! Я не мог вспомнить, в каких случаях получается 128-битный указатель... Виртуальное наследование + множественное наследование + виртуальные функции. Вуаля!
DD>Можно поподробнее? DD>Что находится в каждых 32 битах? и как осуществляется вызов по этом 128 битному адресу?
Это внутреннее дело компилятора, вообще-то. И, честно сказать, я плохо представляю устройство. Могу только предположить.
Множественное наследование: требуется корректировка this для приведения к базовому типу.
struct A { int x; void a(); };
struct B { int y; void b(); };
struct C : A, B { int z; void c(); }
typedef void (C::*CF)();
int main()
{
CF cf = &B::b;
C* pc = new C;
B* pb = pc; // явное приведение типа, с коррекцией адреса
pb->b(); // коррекция не нужна
pc->b(); // коррекция на стадии компиляции: неявное приведение к B*
(pc->*cf)(); // откуда взять коррекцию? - только из cf
}
В принципе, компилятор мог бы завести прокси-метод
struct C: A, B
{
int z;
void call_b() { b(); } // коррекция указателя - здесь
};
int main()
{
CF fb = &B::b; // компилятор подставит &C::call_b
}
Но дело в том, что этот шаг может быть опосредован:
С виртуальными функциями — аналогичная история. Необходимо различать методы, вызываемые статически и динамически.
struct A { int x; void a() {} };
struct B: A { int y; virtual void b() {} };
struct C: B { int z; virtual void b() {} };
typedef void (A::*FA)();
typedef void (B::*FB)();
typedef void (C::*FC)();
int main()
{
B* pb = new C;
FB fa = &A::a;
FB fb = &B::b; // заметьте! мы здесь ничего не знаем про C
pb->a(); // статический вызов
pb->b(); // динамический вызов C::b
(pb->*fa)(); // статический вызов
(pb->*fb)(); // динамический вызов - обращаемся к vfptr/vtbl
}
С виртуальным наследованием — аналогично множественному наследованию, но смещение добывается из подпольной виртуальной функции.
Размер указателя будет зависеть от того, как устроены базовые классы.
Про 16 байт я, возможно, напутал (во всяком случае, сейчас не могу родить комбинацию, в которой это было бы так). Но 12 байт — стопудов.
I>Для тех кто в танке Получить адресс функции метода класса одназначно можно, без всяких хаков. Стандартным синтакисом С++. Проверяно в VC7.1 код совместим работает как, в релиз так, и в дебаг версии с уникодом и без.
Как бывший танкист замечу, что только указатель на метод простого класса можно привести к void*, потому что они имеют одинаковый размер. Через union например, но это хак. А вот указатели сложных классов с множественным наследованием, виртуальными функциями имеют размер 8 или 12 байт. Соответственно, если старшие один-два дворда не равны нулю, то твой хак не будет работать.
Помоему где-то на этом форуме (если мне не изменяет память, то господин Кодт) говорилось, что указатель на данные и указатель на код могут быт разной ширины. Так интересно, какой размер быдет у void*?
Здравствуйте, Plague, Вы писали:
P>Помоему где-то на этом форуме (если мне не изменяет память, то господин Кодт) говорилось, что указатель на данные и указатель на код могут быт разной ширины. Так интересно, какой размер быдет у void*?
Дело в том, что код и данные обычно размещаются в разных местах, и необязательно одинаково адресуемых.
На линейке интеловских процессоров, начиная с i8086 (и далее, включая 16-, 24-, 32- и 64-битные) адрес состоит из пары селектор:смещение.
Если весь код или все данные умещаются в сегмент с одним селектором, то можно единожды загрузить его значение в соответствующий сегментный регистр, и далее оперировать только смещениями.
Указатель, содержащий смещение и "подразумевающий" селектора — называется ближним, near. Указатель, содержащий и селектор, и смещение — называется дальним, far.
Причём, для кода и для данных селекторы могут различаться. Отсюда возникли модели памяти
tiny (flat) — и код, и данные разделяют один и тот же селектор. Все указатели ближние.
small — указатели ближние, но код и данные — в разных сегментах.
compact — данные не умещаются в один сегмент, используются дальние указатели. sizeof(void*) > sizeof(void(*)()).
medium — код не умещается в один сегмент. sizeof(void*) < sizeof(void(*)()).
large — дальние указатели и кода, и данных.
Но и это ещё не всё! Рабочий диапазон значений смещения тоже может варьироваться. Скажем, для IA-64 с плоской моделью можно ограничиться 32-битными смещениями для кода (из расчёта, что его объём — менее 4ГБ) и 64-битными — для кода.
Здравствуйте, Кодёнок, Вы писали:
I>>Для тех кто в танке Получить адресс функции метода класса одназначно можно, без всяких хаков. Стандартным синтакисом С++. Проверяно в VC7.1 код совместим работает как, в релиз так, и в дебаг версии с уникодом и без.
Кё>Как бывший танкист замечу, что только указатель на метод простого класса можно привести к void*, потому что они имеют одинаковый размер.
Нет, в компияоторе, который корректно реализует функциональность указателей на методы классов указатель на метод является "составным" и имеет по этой причне размер, больший чем 'void*'.
Кё>Через union например, но это хак. А вот указатели сложных классов с множественным наследованием, виртуальными функциями имеют размер 8 или 12 байт. Соответственно, если старшие один-два дворда не равны нулю, то твой хак не будет работать.
Но при этом надо заметить, что язык С++ позволяет использовать указатели "базовых" классов (путь они будут "простыми", т.е. без множественных предков) для указания на методы производных классов (пусть там появилось множественное наследование). По этой причине компиляторы всегда вынуждены использовать большие указатели на методы классов. Компиляторы, которые используют 32-хбитные указатели для указания на методы "простых" классов, работают с такими указателями некорректно. Примером является MSVC++, который в режиме "Best-case alwys" действиельно использует 32-битные указатели для методов "простых" классов. Не составляет никакого труда привести пример кода, который из-за этого будет работать некорректно. Для того, чтобы заставить MSVC++ работать с указателями корректно, надо лезть в установки компиляции и выставлять режим 'Any class' (или как его там). При этом указатели на методы сразу вырастут в размере , независимо от того, есть там множественное наследование или нет.
Best regards,
Андрей Тарасевич
Re: Указатель на функцию
От:
Аноним
Дата:
22.12.04 17:41
Оценка:
А немогли бы вы ответить на вопрос в топике (Начнем все сначала. Указатель на функцию) в чем там проблемы.
Здравствуйте, DangerDen, Вы писали:
К>>Во! Точно! Я не мог вспомнить, в каких случаях получается 128-битный указатель... Виртуальное наследование + множественное наследование + виртуальные функции. Вуаля!
DD>Можно поподробнее? DD>Что находится в каждых 32 битах? и как осуществляется вызов по этом 128 битному адресу?
Здравствуйте, Glоbus, Вы писали:
G>Ближе к делу. Если уж ты так реально парился, здается мне что парился ты зря, хотя бы потому, что можно одним движением руки избавиться от вопроса sizeof(void*) < sizeof(void A::*)
G>
E>>>Результат работы под VC++ 7.1: E>>>sizeof(vp): 4, sizeof(mp): 4
АТ>>Такой результат ты получишь только при "агрессивных" установках компиляции, при которых указатели на методы классов в MSVC++ в общем случае работают некорректно. При включении "нормальных" установок компиляции размер указателя на метод класса становится равным 16. Есть "промежуточная" установка, при котором размер будет равен 8.
E>Запускал компиляцию так: E>cl -GX meth_ptr_size.cpp
E>Потом просто запустил meth_ptr_size.exe
Попробуй теперь добавить '/vmg /vmm' или '/vmg /vmv'.
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Здравствуйте, eao197, Вы писали:
АТ>Попробуй теперь добавить '/vmg /vmm' или '/vmg /vmv'.
Попробовал. /vmm: 8 байт, /vmv: 16 байт.
Однако, фокус-то здесь не только в /vmm (/vmv), а еще и в /vmg, которая должна использоваться, если указатель на метод определяется еще до того, как будет определен сам класс. В моем примере класс определен, поэтому компилятор смог сам определить оптимальный размер указателя на метод (4 байта).
И скомпилировать с найтройками по-умолчанию:
cl -GX meth_ptr_size-2.cpp
То получается:
sizeof(vp): 4, sizeof(mp): 16
Т.е. в этом случае компилятор настраивается на худший вариант (т.к. он не знает, что из себя представляет класс A) и выбирает максимальный размер указателя.
Сам я про эту фишку VC++ не знал, спасибо.
Компиляторы GNU C++ 3.3.3, Borland C++ 5.6, Digital Mars C++ v.8.35 на это не заморачиваются, у них всегда указатель на метод одного и того же размера (8 у GNU C++, 12 у BC++, 4 у DMC).
Для себя сделал вывод, что с компилятором VC++ можно поиметь приключения вот в таком случае.
Файл a_forward.hpp
class A;
typedef void (A::*method_of_A)();
void
do_something( A * a, method_of_A m );
Файл do_something.cpp
#include"a_forward.hpp"void
do_something( A * a, method_of_A m )
{
(a->*m)();
}
Файл a_implementation.hpp
class A
{
public :
void f();
};
Файл main.cpp
#include"a_implementation.hpp"#include"a_forward.hpp"void
A::f() {}
int
main()
{
A a;
do_something( &a, &A::f );
return 0;
}
Компилируем: cl -o test.exe main.cpp do_something.cpp
Запускаем и получаем ошибку.
Ни с GNU C++, ни с Borland C++, ни с Digital Mars C++, при использовании настроек по-умолчанию проблем нет.
IMHO в Microsoft перемудрили.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
АТ>Указатели на методы классов в С++ обладают рядом уникальных свойств. На них стандартом языка накладываются определенные требования, удовлетворить которым можно только путем помещения в указатель дополнительной информации, наряду с банальным адресом точки входа в метод. По этой причине, если на некоторой платформа размер адреса равен 32-м битам, то размер корректно реализованного указателя на метод класса всегда заведомо больше 32-х бит (64 бита, например)....
Я не специалист по внутреннему устройству компиляторов, но это утверждение представляется мне излишне категоричным. Теоритически, вполне можно представить себе ситуацию, когда указатель на метод является всего лишь "дескриптором", указателем на структуру, в которой и находятся все необходимые для вызова значения. Тогда указатель на метод вполне может оказаться 32-х битовым, а вот размер вспомогательной структуры -- это уже другой вопрос. Может быть поэтому указатели на методы в Digital Mars C++ имеют размер 4 байта.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Может быть поэтому указатели на методы в Digital Mars C++ имеют размер 4 байта.
Digital Mars C++, uses a different optimization. For single-inheritance classes, a member function pointer is just the address of the function. When more complex inheritance is involved, the member function pointer points to a 'thunk' function, which performs the necessary adjustments to the this pointer, and then calls the real member function. One of these little thunk functions is created for every member function that's involved in multiple inheritance. This is the most efficient implementation.
Здравствуйте, eao197, Вы писали:
E>Однако, фокус-то здесь не только в /vmm (/vmv), а еще и в /vmg, которая должна использоваться, если указатель на метод определяется еще до того, как будет определен сам класс. В моем примере класс определен, поэтому компилятор смог сам определить оптимальный размер указателя на метод (4 байта).
Дело в том (это тут уже обсуждалось) что в общем случае определить "оптимальный" размер указателя автоматически невозможно. В С++ разрешается приведение указателей на члены классов как вверх, так и вниз по иерархии объектов (в соответсвии с правилами контравариантности). Для того, чтобы автоматически определить правильный размер указателя компилятор должден иметь возможность проанализировать структуру абсолютно всей иерархии объектов программы. Это, разумеется, в большинстве случаев исключительно трудно или вообще невозможно.
Вот простенький пример
struct A {
int i;
A() : i(1) {}
};
struct B {
int i;
B() : i(2) {}
};
struct C : A, B {
int i;
C() : i(3) {}
void print() { std::cout << i << std::endl; }
};
void call(B* pb, void (B::*pm)()) {
(pb->*pm)();
}
int main() {
C c;
call(&c, static_cast<void (B::*)()>(&C::print));
}
Это — легальный С++ код. И при выполнении из функции 'call' должен вызваться метод 'C::print' с правильно сформированным указателем 'this' класса 'C'. Т.е. в результате должно напечататься '3'. Однако, если попробовать скомпилировать этот пример в режиме автоматического определения размера указателя, то печатать он будет всякую белиберду. А вот при компиляции с /vmm или /vmv все будет работать корректно. Т.е. надо иметь в виду, что MSVC++-шный режим автоматического определения размера указателя в общем случае приводит к некорректному коду. А именно, проблемы могут возникнуть при приведении указателя на метод вверх по иерархии (как в моем примере).
E>Сам я про эту фишку VC++ не знал, спасибо. E>Компиляторы GNU C++ 3.3.3, Borland C++ 5.6, Digital Mars C++ v.8.35 на это не заморачиваются, у них всегда указатель на метод одного и того же размера (8 у GNU C++, 12 у BC++, 4 у DMC).
Да, причем для полной гарантии корректной работы указателей другого выхода просто нет. Указатель на метод всегда должен быть больше по размеру, чем "простой" указатель.
E>Для себя сделал вывод, что с компилятором VC++ можно поиметь приключения вот в таком случае.
Вот вот, еще один пример.
E>IMHO в Microsoft перемудрили.
В MS сделали фичу, которой надо пользоваться с определенной долей осторожности. В принципе, полезность в этой фиче имеется, ибо в ситуациях с одиночным (и только одиночным) обычным наследованием действительно можно обойтись "маленькими" указателями на методы. В многих программах только такое наследование и используется, что дает возможность сэкономить память и повысить производительность вызова. Вот чего не стоило бы делать MS, так это придавать этой фиче эдакий дух "универсальной автоматичности". Это не должно делаться автоматически, это должен быть сознательный выбор программиста, имеющего представление о потенциальных граблях такого выбора.
Здравствуйте, korzhik, Вы писали:
E>>Может быть поэтому указатели на методы в Digital Mars C++ имеют размер 4 байта.
K>
K>Digital Mars C++, uses a different optimization. For single-inheritance classes, a member function pointer is just the address of the function. When more complex inheritance is involved, the member function pointer points to a 'thunk' function, which performs the necessary adjustments to the this pointer, and then calls the real member function. One of these little thunk functions is created for every member function that's involved in multiple inheritance. This is the most efficient implementation.
Это описание наводит на мысль что Digital Mars C++ реализован в соответствии со старым черновым вариантом спецификации языка, в котором еще не было разрешения приводить указатели на методы классов вверх по иерархии (при помощи 'static_cast'). У кого-нибудь есть возможность попробовать скомпилировать и выполнить код вот из этого
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Это описание наводит на мысль что Digital Mars C++ реализован в соответствии со старым черновым вариантом спецификации языка, в котором еще не было разрешения приводить указатели на методы классов вверх по иерархии (при помощи 'static_cast'). У кого-нибудь есть возможность попробовать скомпилировать и выполнить код вот из этого
АТ>Дело в том (это тут уже обсуждалось) что в общем случае определить "оптимальный" размер указателя автоматически невозможно. В С++ разрешается приведение указателей на члены классов как вверх, так и вниз по иерархии объектов (в соответсвии с правилами контравариантности).
IMHO приведение вверх по иерархии недопустимо в общем случае. См. ниже. Хотя я могу ошибаться, т.к. не являюсь знатоком стандарта C++ и не знаю правила контравариантности. Но может быть вы и правы.
AT>Для того, чтобы автоматически определить правильный размер указателя компилятор должден иметь возможность проанализировать структуру абсолютно всей иерархии объектов программы. Это, разумеется, в большинстве случаев исключительно трудно или вообще невозможно.
АТ>Вот простенький пример
АТ>
АТ>struct A {
АТ> int i;
АТ> A() : i(1) {}
АТ>};
АТ>struct B {
АТ> int i;
АТ> B() : i(2) {}
АТ>};
АТ>struct C : A, B {
АТ> int i;
АТ> C() : i(3) {}
АТ> void print() { std::cout << i << std::endl; }
АТ>};
АТ>void call(B* pb, void (B::*pm)()) {
АТ> (pb->*pm)();
АТ>}
АТ>int main() {
АТ> C c;
АТ> call(&c, static_cast<void (B::*)()>(&C::print));
АТ>}
АТ>
АТ>Это — легальный С++ код. И при выполнении из функции 'call' должен вызваться метод 'C::print' с правильно сформированным указателем 'this' класса 'C'. Т.е. в результате должно напечататься '3'. Однако, если попробовать скомпилировать этот пример в режиме автоматического определения размера указателя, то печатать он будет всякую белиберду. А вот при компиляции с /vmm или /vmv все будет работать корректно. Т.е. надо иметь в виду, что MSVC++-шный режим автоматического определения размера указателя в общем случае приводит к некорректному коду. А именно, проблемы могут возникнуть при приведении указателя на метод вверх по иерархии (как в моем примере).
Позволю себе не согласиться с легальностью этого кода. При компиляции VC++ с настройками по умолчанию компилятор выдает предупреждение (на строку call(&c...)):
tarasevich.cpp(25) : warning C4407: cast between different pointer to member representations, compiler may generate incorrect code
Если же static_cast убрать, то скомпилировать ваш пример вообще не получиться:
tarasevich.cpp(25) : error C2664: 'call' : cannot convert parameter 2 from 'void (__thiscall C::* )(void)' to 'void (__thiscall B::* )(void)'
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
Поскольку метод наследника (класса C) это в общем случае не метод базового класса (класса В).
Года 4 назад мне потребовалось плотно поиспользовать указатели на методы класса и я с удивлением обнаружил, что компилятор не позволяет просто так приводить указатель на метод производного класса к методу базового класса. Стал разбираться почему и вычитал, сейчас не помню у кого, что это делать нельзя. С тех пор не делаю и проблем не имею.
Когда VC++ знает описание класса и видит определение типа указателя на метод класса он может сделать разумные предположения о необходимом размере указателя на методы именно этого класса (включая и унаследованные методы). Если у нас есть класс A, на метод которого мы хотим определить указатель, и VC++ видит описание этого класса (значит и описания всех его родительских классов), то VC++ точно знает, какой размер указателя ему для этого нужен. Если же описания класса A в точке определения указателя на метод A еще нет (пример описан в MSDN в документации по /vmb и /vmg), то VC++ не знает про наследование A. Поэтому VC++ не знает, будет ли указатель указывать на метод самого A, будет ли указывать на метод его базового класса, будет ли этот базовый класс виртуальным базовым классом или одним из базовых при множественном наследовании, и т.д. и т.п. Поэтому VC++ выбирает для неизвестных классов максимальный размер указателя.
Проблемы начинаются тогда, когда за методы класса начинают выдавать методы его наследников. И опции /vmm, /vmv просто спасают ноги программистов от огнестрельных ранений, но причина проблем в ошибке программиста, которая подавляется через static_cast и /vmv.
E>>IMHO в Microsoft перемудрили.
АТ>В MS сделали фичу, которой надо пользоваться с определенной долей осторожности....
Ну да, всего лишь еще одна фича. Про которую нужно не только знать, но и помнить. До сегодняшнего дня я про нее вообще не знал.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
E>Я не специалист по внутреннему устройству компиляторов, но это утверждение представляется мне излишне категоричным. Теоритически, вполне можно представить себе ситуацию, когда указатель на метод является всего лишь "дескриптором", указателем на структуру, в которой и находятся все необходимые для вызова значения. Тогда указатель на метод вполне может оказаться 32-х битовым, а вот размер вспомогательной структуры -- это уже другой вопрос. Может быть поэтому указатели на методы в Digital Mars C++ имеют размер 4 байта.
Кстати, да Когда мне ооочень нужно было иметь указатель размером void* на метод, я создал статические переменные, хранящие указатели на все методы, адрес которых я беру, и брал указатель на эту статическую переменную. Этот метод кстати, отлично компилируется и работает с любыми настройками VC++, если опять же не кастовать вверх по иерархии.
Для автоматизации кастинга к void* и обратно и вызова можно написать набор шаблонных классов и функций, которые будут где-нибудь в статическом массиве будут хранить соответствия между void*-ом и указателем на члены, добавляя в рантайме в момент приведения типа.
Здравствуйте, eao197, Вы писали:
АТ>>Дело в том (это тут уже обсуждалось) что в общем случае определить "оптимальный" размер указателя автоматически невозможно. В С++ разрешается приведение указателей на члены классов как вверх, так и вниз по иерархии объектов (в соответсвии с правилами контравариантности).
E>IMHO приведение вверх по иерархии недопустимо в общем случае. См. ниже. Хотя я могу ошибаться, т.к. не являюсь знатоком стандарта C++ и не знаю правила контравариантности. Но может быть вы и правы.
Согласно стандарту языка, приведение такого указателя вверх по иерархии разрешается всегда, когда разрешается приведение вниз. Этого, в принципе, достаточно для того, чтобы сказать, что такое приведение допустимо в общем случае.
АТ>>... АТ>>Это — легальный С++ код. И при выполнении из функции 'call' должен вызваться метод 'C::print' с правильно сформированным указателем 'this' класса 'C'. Т.е. в результате должно напечататься '3'. Однако, если попробовать скомпилировать этот пример в режиме автоматического определения размера указателя, то печатать он будет всякую белиберду. А вот при компиляции с /vmm или /vmv все будет работать корректно. Т.е. надо иметь в виду, что MSVC++-шный режим автоматического определения размера указателя в общем случае приводит к некорректному коду. А именно, проблемы могут возникнуть при приведении указателя на метод вверх по иерархии (как в моем примере).
E>Позволю себе не согласиться с легальностью этого кода. При компиляции VC++ с настройками по умолчанию компилятор выдает предупреждение (на строку call(&c...)):
E>
E>tarasevich.cpp(25) : warning C4407: cast between different pointer to member representations, compiler may generate incorrect code
Этот код соверешено легален. А предупреждение — это ни что иное, как недостоток выбранного режима компиляции. Версия MSVC++ 6 проглатывает этот код молча, но программа потом работает неправильно (при умолчательных установках компиляции). Ты, по-видимому, используешь MSVC++ 7, в котором теперь сделали предупреждение. Я бы на месте MS вообще отказался бы компилировать код и предложил бы пользоваетелю сменить модель указателя.
Это хорошо, что компилятор сообщает о проблеме, но к легальности этого кода это никакого отношения не имеет. Код легален, а пердупреждение говорит только о том, что в данном режиме компиляции данный компилятор не в состоянии его правильно скомпилировать.
E>Если же static_cast убрать, то скомпилировать ваш пример вообще не получиться:
E>
E>tarasevich.cpp(25) : error C2664: 'call' : cannot convert parameter 2 from 'void (__thiscall C::* )(void)' to 'void (__thiscall B::* )(void)'
E> Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
'static_cast' убирать, разумеется, не надо. Правила контравариантности разрешают неявное приведение вниз по иерархии и требуют 'static_cast' для приведения вверх по иерерхии. Симметричным образом, например, правила ковариантности (управляющие приведением обычных указателей) наоборот разрешают неявное приведение вверх, но требуют явного каста для приведения вниз.
E>Поскольку метод наследника (класса C) это в общем случае не метод базового класса (класса В).
Да, но язык С++ и не требует этого при выполнении приведения. Язык С++ требует наличия члена в классе только в момент обращения к этому члену через указатель, но не в моемент преобразования типов.
E>Года 4 назад мне потребовалось плотно поиспользовать указатели на методы класса и я с удивлением обнаружил, что компилятор не позволяет просто так приводить указатель на метод производного класса к методу базового класса. Стал разбираться почему и вычитал, сейчас не помню у кого, что это делать нельзя. С тех пор не делаю и проблем не имею.
Это уже зависит от того, что ты имеешь в виду под "нельзя".
"Нельзя = не рекомендуется, т.к. опасно"? Это действительно так, но это лишь означает, что использовать данную возможноть надо только в том случае, если это действительно необходимо. Точно так же и обычные указатели на объекты приводит вверх по иерархии "нельзя" (по аналогичной причине), тем не менее такая возможность в языке есть. Кроме того, подобное "нельзя" в этой дискуссии, вследствие ее специфики, силы не имеет.
"Нельзя = запрещено спецификацией языка"? Это соврешенно не верно. Спецификация языка явно разрешает такое приведение. Когда-то давно в самой первой версии черновика стандарта языка это было запрещено. Так что если ты читал что-то старинное, то ты действительно можешь наткнутся на такое "нельзя". Но уже во втором черновике (не говоря уже о финальном стандарте) такое приведение было разрешено.
E>Когда VC++ знает описание класса и видит определение типа указателя на метод класса он может сделать разумные предположения о необходимом размере указателя на методы именно этого класса (включая и унаследованные методы). Если у нас есть класс A, на метод которого мы хотим определить указатель, и VC++ видит описание этого класса (значит и описания всех его родительских классов), то VC++ точно знает, какой размер указателя ему для этого нужен. Если же описания класса A в точке определения указателя на метод A еще нет (пример описан в MSDN в документации по /vmb и /vmg), то VC++ не знает про наследование A. Поэтому VC++ не знает, будет ли указатель указывать на метод самого A, будет ли указывать на метод его базового класса, будет ли этот базовый класс виртуальным базовым классом или одним из базовых при множественном наследовании, и т.д. и т.п. Поэтому VC++ выбирает для неизвестных классов максимальный размер указателя. E>Проблемы начинаются тогда, когда за методы класса начинают выдавать методы его наследников. И опции /vmm, /vmv просто спасают ноги программистов от огнестрельных ранений, но причина проблем в ошибке программиста, которая подавляется через static_cast и /vmv.
Последнее соврешенно не верно. Никакая "ошибка программиста" через 'static_cast' не подавляется. 'static_cast', по своей идее, не может подавлять никаких "ошибок программиста" (для этого предназначен 'reinterpret_cast' . Использованная мною конверсия является соврешенно легальной функциональностью 'static_cast', четко описанной в отдельном разделе стандарта 5.2.9/9. Меня всегда удивляло, почему программисты с достаточной степенью готовности вопринимают идею ковариантности для обычных указателей, а вот в существовоание контравариантности дял указателей на члены не верят и считают, что без хака тут не обойтись.
Здравствуйте, eao197, Вы писали:
АТ>>Указатели на методы классов в С++ обладают рядом уникальных свойств. На них стандартом языка накладываются определенные требования, удовлетворить которым можно только путем помещения в указатель дополнительной информации, наряду с банальным адресом точки входа в метод. По этой причине, если на некоторой платформа размер адреса равен 32-м битам, то размер корректно реализованного указателя на метод класса всегда заведомо больше 32-х бит (64 бита, например)....
E>Я не специалист по внутреннему устройству компиляторов, но это утверждение представляется мне излишне категоричным. Теоритически, вполне можно представить себе ситуацию, когда указатель на метод является всего лишь "дескриптором", указателем на структуру, в которой и находятся все необходимые для вызова значения. Тогда указатель на метод вполне может оказаться 32-х битовым, а вот размер вспомогательной структуры -- это уже другой вопрос. Может быть поэтому указатели на методы в Digital Mars C++ имеют размер 4 байта.
Я с этим согласен. Я только замечу, что в моем вышепроцитированном сообщении речь шла, скорее, о количестве информации, которая необходима для правильного вызова метода класса через указатель, т.е. о некоем "совокупном" размере указателя, включающем в себя все его "кусочки", где бы они ни были разбросаны.
На такой платформе, наверное, можно будет более-менее безопасно приводить указатели на методы класстов к типу 'void*', хоть это и будет по-прежнему будет хаком.
АТ>Этот код соверешено легален. А предупреждение — это ни что иное, как недостоток выбранного режима компиляции. Версия MSVC++ 6 проглатывает этот код молча, но программа потом работает неправильно (при умолчательных установках компиляции). Ты, по-видимому, используешь MSVC++ 7, в котором теперь сделали предупреждение. Я бы на месте MS вообще отказался бы компилировать код и предложил бы пользоваетелю сменить модель указателя.
АТ>Это хорошо, что компилятор сообщает о проблеме, но к легальности этого кода это никакого отношения не имеет. Код легален, а пердупреждение говорит только о том, что в данном режиме компиляции данный компилятор не в состоянии его правильно скомпилировать.
Вот, что я компилировал:
#include <iostream>
struct A {
int i;
A() : i(1) {}
};
struct B {
int i;
B() : i(2) {}
};
struct C : A, B {
int i;
C() : i(3) {}
void print() { std::cout << i << std::endl; }
};
void call(B* pb, void (B::*pm)()) {
(pb->*pm)();
}
int main() {
C c;
call(&c, &C::print );
return 0;
}
Результат компиляции VC++ 7.1 я уже приводил.
Результат компиляции Borland C++:
Borland C++ 5.6 for Win32 Copyright (c) 1993, 2002 Borland
tarasevich.cpp:
Error E2034 tarasevich.cpp 25: Cannot convert 'void (C::*)()' to 'void (B::*)()' in function main()
Error E2342 tarasevich.cpp 25: Type mismatch in parameter 'pm' (wanted 'void (B::*)()', got 'void (C::*)()') in function main()
*** 2 errors in Compile ***
Результат компиляции GNU C++ 3.3.3 (cygwin):
tarasevich.cpp: In function `int main()':
tarasevich.cpp:25: error: cannot convert `void (C::*)()' to `void (B::*)()' for
argument `2' to `void call(B*, void (B::*)())'
Comeau C/C++ 4.3.3 (Aug 6 2003 15:13:37) for ONLINE_EVALUATION_BETA1
Copyright 1988-2003 Comeau Computing. All rights reserved.
MODE:strict errors C++
"ComeauTest.c", line 25: error: argument of type "void (C::*)()" is incompatible
with parameter of type "void (B::*)()"
call(&c, &C::print );
^
1 error detected in the compilation of "ComeauTest.c".
In strict mode, with -tused, Compile failed
Поэтому я думаю, что проблема не в особенностях конкретного компилятора, а в корректности кода.
АТ>'static_cast' убирать, разумеется, не надо. Правила контравариантности разрешают неявное приведение вниз по иерархии и требуют 'static_cast' для приведения вверх по иерерхии. Симметричным образом, например, правила ковариантности (управляющие приведением обычных указателей) наоборот разрешают неявное приведение вверх, но требуют явного каста для приведения вниз.
E>>Поскольку метод наследника (класса C) это в общем случае не метод базового класса (класса В).
АТ>Да, но язык С++ и не требует этого при выполнении приведения. Язык С++ требует наличия члена в классе только в момент обращения к этому члену через указатель, но не в моемент преобразования типов.
Вот следствие из этого замечания:
#include <stdio.h>
struct A {
int i;
A() : i(1) {}
};
struct B {
int i;
B() : i(2) {}
};
typedef void (B::*meth_t)();
struct C : A, B {
int i;
C() : i(3) {}
void print() { printf( "C::print: %d\n", i ); }
};
struct D : B {
int i;
D() : i(4) {}
void print() { printf( "D::print: %d\nFormat C: complete!\n", i ); }
};
void call(B* pb, meth_t pm ) {
(pb->*pm)();
}
int main() {
meth_t pm = static_cast< meth_t >( &C::print );
C c;
call( &c, pm );
D d;
call( &d, pm );
return 0;
}
Результатом работы является:
C::print: 3
C::print: 4
Т.е. для объекта типа D вызывается метод из типа C! Причем типы D и C между собой не связаны отношением наследования.
Понятно, что в этом примере я передергиваю: нельзя применять результат static_cast< meth_t >( &C::print ) к объектам, отличным от C. Но ведь согласно синтаксису языка здесь все нормально. Т.е. это вполне легальный код, с нелегальными результатами, однако.
Но проблем бы не было, если бы я не использовал static_cast. А со static_cast я силовым методом принуждаю C++ компилятор считать этот код легальным.
E>>Проблемы начинаются тогда, когда за методы класса начинают выдавать методы его наследников. И опции /vmm, /vmv просто спасают ноги программистов от огнестрельных ранений, но причина проблем в ошибке программиста, которая подавляется через static_cast и /vmv.
АТ>Последнее соврешенно не верно. Никакая "ошибка программиста" через 'static_cast' не подавляется. 'static_cast', по своей идее, не может подавлять никаких "ошибок программиста" (для этого предназначен 'reinterpret_cast' . Использованная мною конверсия является соврешенно легальной функциональностью 'static_cast', четко описанной в отдельном разделе стандарта 5.2.9/9. Меня всегда удивляло, почему программисты с достаточной степенью готовности вопринимают идею ковариантности для обычных указателей, а вот в существовоание контравариантности дял указателей на члены не верят и считают, что без хака тут не обойтись.
Посмотрел пункт 5.2.9/9 стандарта, а заодно и ссылку оттуда на пункт 4.11. Казуистика еще та, сразу вспомнилась русская пословица про "закон, что дышло..." + еще мои (не)знания английского языка К сожалению, стандарт C++ далек от совершенства, допускает различные варианты интерпритации (что видно на особенностях разных компиляторов).
Но для себя я сделал такой вывод: static_cast для указателей на члены класса можно применять, только если есть разрешенное обычное преобразование указателей на члены класса. А разрешенность преобразований указателей на члены классов описывается в 4.11. И для меня оттуда следует, что например указатель на член класса B можно привести к указателю на член класса D, если класс B является базовым для D. Что понятно, т.к. все атрибуты/методы из B находятся в D. Следовательно, обратное преобразование возможно, но необходимо, чтобы преобразуемый указатель указывал на атрибут/метод из B, а не из D. Исходя из этого два следующих примера будут вполне корректны:
#include <iostream>
struct A {
int i;
A() : i(1) {}
};
struct B {
int i;
B() : i(2) {}
void print() { std::cout << i << std::endl; }
};
struct C : A, B {
int i;
C() : i(3) {}
};
void call(B* pb, void (B::*pm)()) {
(pb->*pm)();
}
int main() {
C c;
call(&c, &C::print );
return 0;
}
Что понятно, т.к. C::print — это синоним для B::print.
А вот здесь static_cast действительно необходим:
#include <stdio.h>
struct A {
int i;
A() : i(1) {}
};
struct B {
int i;
B() : i(2) {}
virtual void print() { printf( "B::print: %d\n", i ); }
};
struct C : A, B {
int i;
C() : i(3) {}
virtual void print() { printf( "C::print: %d\n", i ); }
};
void call(B* pb, void (B::*pm)()) {
(pb->*pm)();
}
int main() {
C c;
call(&c, static_cast< void (B::*)() >( &C::print ) );
return 0;
}
И использование static_cast здесь вполне легально, т.к. метод print есть в классе B, базовом для C.
Ну и в догонку пример того, как static_cast (будучи, в определенной степени синонимом преобразования типов в стиле C) позволяет легко прострелить себе ногу еще в одном случае:
char c = ...;
unsigned int u = static_cast< unsigned int >( c );
В этом легальном C++ коде мы получим проблемы, если тип char будет знаковым, а значение c — отрицательным. И это как раз тот случай, когда "Нельзя = не рекомендуется, т.к. опасно" лично для меня более важно, чем "Нельзя = запрещено спецификацией языка". Но это уже совсем другая история.
Ну а чтобы закрыть исходную тему, поднятую г.Якубовским, цитата из стандарта C++ (пункт 4.11/2, сноска 52 на странице 61):
... In particular, a pointer to member cannot be converted to a void*.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
АТ>>Этот код соверешено легален. А предупреждение — это ни что иное, как недостоток выбранного режима компиляции. Версия MSVC++ 6 проглатывает этот код молча, но программа потом работает неправильно (при умолчательных установках компиляции). Ты, по-видимому, используешь MSVC++ 7, в котором теперь сделали предупреждение. Я бы на месте MS вообще отказался бы компилировать код и предложил бы пользоваетелю сменить модель указателя.
АТ>>Это хорошо, что компилятор сообщает о проблеме, но к легальности этого кода это никакого отношения не имеет. Код легален, а пердупреждение говорит только о том, что в данном режиме компиляции данный компилятор не в состоянии его правильно скомпилировать.
E>Вот, что я компилировал: E>... E>Результат компиляции VC++ 7.1 я уже приводил. E>Результат компиляции Borland C++: E>... E>Поэтому я думаю, что проблема не в особенностях конкретного компилятора, а в корректности кода.
Не понимаю. Я же говорю о моем коде в его исходной форме. Ты же компилировал свою модифицированную версию кода, из которой ты зачем-то убрал 'static_cast'. Без 'static_cast' код действительно некорректен, компилироваться не должен и не будет, о чем я уже говорил в моем предыдущем сообщении. Что именно ты хочешь сказать, приводя здесь результаты компиляции модифицированного кода?
АТ>>'static_cast' убирать, разумеется, не надо. Правила контравариантности разрешают неявное приведение вниз по иерархии и требуют 'static_cast' для приведения вверх по иерерхии. Симметричным образом, например, правила ковариантности (управляющие приведением обычных указателей) наоборот разрешают неявное приведение вверх, но требуют явного каста для приведения вниз.
E>>>Поскольку метод наследника (класса C) это в общем случае не метод базового класса (класса В).
АТ>>Да, но язык С++ и не требует этого при выполнении приведения. Язык С++ требует наличия члена в классе только в момент обращения к этому члену через указатель, но не в моемент преобразования типов.
E>Вот следствие из этого замечания:
E>
E>#include <stdio.h>
E>struct A {
E> int i;
E> A() : i(1) {}
E>};
E>struct B {
E> int i;
E> B() : i(2) {}
E>};
E>typedef void (B::*meth_t)();
E>struct C : A, B {
E> int i;
E> C() : i(3) {}
E> void print() { printf( "C::print: %d\n", i ); }
E>};
E>struct D : B {
E> int i;
E> D() : i(4) {}
E> void print() { printf( "D::print: %d\nFormat C: complete!\n", i ); }
E>};
E>void call(B* pb, meth_t pm ) {
E> (pb->*pm)();
E>}
E>int main() {
E> meth_t pm = static_cast< meth_t >( &C::print );
E> C c;
E> call( &c, pm );
E> D d;
E> call( &d, pm );
E> return 0;
E>}
E>
E>Результатом работы является: E>
E>C::print: 3
E>C::print: 4
E>Т.е. для объекта типа D вызывается метод из типа C! Причем типы D и C между собой не связаны отношением наследования.
Ну так ты нарушил требоание спецификации языка и получил код, приводящий к неопределенному поведению. Ничего удивительного в этом нет.
E>Понятно, что в этом примере я передергиваю: нельзя применять результат static_cast< meth_t >( &C::print ) к объектам, отличным от C.
Совершенно верно.
E>Но ведь согласно синтаксису языка здесь все нормально.
Согласно синтаксису — да, все нормально. Но какое это имеет значение?
E>Т.е. это вполне легальный код, с нелегальными результатами, однако.
Это так называемый well-formed код. А вот легальным я бы его называть не стал. Хотя это уже зависит от твоей трактовки слова "легальный".
E>Но проблем бы не было, если бы я не использовал static_cast. А со static_cast я силовым методом принуждаю C++ компилятор считать этот код легальным.
Прекрасно. Но это не аргумент. В С++ имеется бесчисленное множество мест, где синтаксически правильный код является ошибочным. И где полезные в одних контекстах свойства могут приводить к "сломанному" коду в других. Таков С++.
E>>>Проблемы начинаются тогда, когда за методы класса начинают выдавать методы его наследников. И опции /vmm, /vmv просто спасают ноги программистов от огнестрельных ранений, но причина проблем в ошибке программиста, которая подавляется через static_cast и /vmv.
АТ>>Последнее соврешенно не верно. Никакая "ошибка программиста" через 'static_cast' не подавляется. 'static_cast', по своей идее, не может подавлять никаких "ошибок программиста" (для этого предназначен 'reinterpret_cast' . Использованная мною конверсия является соврешенно легальной функциональностью 'static_cast', четко описанной в отдельном разделе стандарта 5.2.9/9. Меня всегда удивляло, почему программисты с достаточной степенью готовности вопринимают идею ковариантности для обычных указателей, а вот в существовоание контравариантности дял указателей на члены не верят и считают, что без хака тут не обойтись.
E>Посмотрел пункт 5.2.9/9 стандарта, а заодно и ссылку оттуда на пункт 4.11. Казуистика еще та, сразу вспомнилась русская пословица про "закон, что дышло..." + еще мои (не)знания английского языка К сожалению, стандарт C++ далек от совершенства, допускает различные варианты интерпритации (что видно на особенностях разных компиляторов).
E>Но для себя я сделал такой вывод: static_cast для указателей на члены класса можно применять, только если есть разрешенное обычное преобразование указателей на члены класса. А разрешенность преобразований указателей на члены классов описывается в 4.11. И для меня оттуда следует, что например указатель на член класса B можно привести к указателю на член класса D, если класс B является базовым для D. Что понятно, т.к. все атрибуты/методы из B находятся в D. Следовательно, обратное преобразование возможно, но необходимо, чтобы преобразуемый указатель указывал на атрибут/метод из B, а не из D.
Нет, вот этого последноего требования в стандарте нет. Стандарт разрешает 'static_cast' преобразование любых указателей типа 'pointer to member of D' к соотвествующему типу 'pointer to member of B', если обратное преобразование удовлетворяет требованиям стандартного преобразования, описанного в 4.11. Никаких ограничений на то, куда именно указывает исходный указатель в момент преобразования не накладывается.
Дополнительные ограничения появляются только в 5.5/4, где сказано, что динамический тип объекта, стоящего слева от операторов '.*' или '->*' должен содержать указуемый член, иначе — поведение не определено. Т.е. ограничения на то, куда именно указывает указатель, вступают в силу только в момент разадресации этого указателя, но не раньше. Это именно то требование, которое ты нарушил в своем примере выше.
E>Ну и в догонку пример того, как static_cast (будучи, в определенной степени синонимом преобразования типов в стиле C) позволяет легко прострелить себе ногу еще в одном случае:
E>
E>char c = ...;
E>unsigned int u = static_cast< unsigned int >( c );
E>
E>В этом легальном C++ коде мы получим проблемы, если тип char будет знаковым, а значение c — отрицательным. И это как раз тот случай, когда "Нельзя = не рекомендуется, т.к. опасно" лично для меня более важно, чем "Нельзя = запрещено спецификацией языка". Но это уже совсем другая история.
Не совсем понимаю, о каких "проблемах" идет речь. Если значение 'c' отрицательно, то сработает арифметика по модулю, который подчиняются беззнаковые типы, и переменная 'u' проинициализируется значенем 'UINT_MAX + c + 1'. 'static_cast' тут никаким боком совершенно ни при чем. Приведенный тобой код строго эквивалентен простому
unsigned int u = c;
т.е. никакого отношения к 'static_cast' происходящее не имеет вообще. Подводным камнем в этом примере является только зависящая от реализации знаковость или беззнаковость типа 'char'. При чем здесь 'static_cast'?
Здравствуйте, Андрей Тарасевич, Вы писали:
АТ>Не понимаю. Я же говорю о моем коде в его исходной форме. Ты же компилировал свою модифицированную версию кода, из которой ты зачем-то убрал 'static_cast'. Без 'static_cast' код действительно некорректен, компилироваться не должен и не будет, о чем я уже говорил в моем предыдущем сообщении. Что именно ты хочешь сказать, приводя здесь результаты компиляции модифицированного кода?
Очевидно, в какой-то момент я не понял, почему VC++ 6.0 проглатывает какой-то код, а VC++ 7.1 на нем выдает ошибку. Т.к. со static_cast все компиляторы компилируют твой исходный код без ошибок, то я подумал, что речь должна идти о варианте без static_cast. И ошибся.
Приводя результаты компиляции я хотел показать, что без static_cast все из доступных мне компиляторов выдали ошибку.
АТ>Прекрасно. Но это не аргумент. В С++ имеется бесчисленное множество мест, где синтаксически правильный код является ошибочным. И где полезные в одних контекстах свойства могут приводить к "сломанному" коду в других. Таков С++.
АТ>Нет, вот этого последноего требования в стандарте нет. Стандарт разрешает 'static_cast' преобразование любых указателей типа 'pointer to member of D' к соотвествующему типу 'pointer to member of B', если обратное преобразование удовлетворяет требованиям стандартного преобразования, описанного в 4.11. Никаких ограничений на то, куда именно указывает исходный указатель в момент преобразования не накладывается.
АТ>Дополнительные ограничения появляются только в 5.5/4, где сказано, что динамический тип объекта, стоящего слева от операторов '.*' или '->*' должен содержать указуемый член, иначе — поведение не определено. Т.е. ограничения на то, куда именно указывает указатель, вступают в силу только в момент разадресации этого указателя, но не раньше. Это именно то требование, которое ты нарушил в своем примере выше.
Андрей, я признаю, что по букве закона вы полностью правы.
Проблема видимо в том, что я не расставил сразу акценты. Итак, вы правы, что применение static_cast разрешено стандартом и может, а временами должно, использоваться для преобразования указателей на члены классов.
Моя позиция состоит в том, что в некоторых случаях применение static_cast вообще, и для преобразования указателей на члены класса в частности, приводит к проблемам. И этих проблем можно было бы легко избежать, не используя static_cast.
В данном конкретном случае я начал возражать потому, что ты пытаешься выдать метод C::print за метод класса B. Оставим на время то, что через static_cast это разрешено по стандарту. Проблема в том, что функция call получает указатель на метод класса B. И она вправе применить этот указатель не только к тому объекту, который ей передали в качестве первого аргумента, но и к любому другому объекту типа B. Например, функция call может создать собственный объект типа B и через указатель попробует вызывать у него метод. Которого нет в B. Конечно, можно сейчас сказать, что это был пример, что функция call была сделана так, чтобы вызывать метод только у того объекта, который ей передан в качестве аргумента и т.д. Все это так. Но так же верно и то, что полученный через static_cast указатель может быть использован сам по себе. Он же физически не связан с объектом класса C для которого был получен. Моя позиция в том, что применив здесь static_cast мы заткнули рот компилятору, который пытался сказать нам, что в данном конкретном случае (не вообще) мы не правы.
E>>Ну и в догонку пример того, как static_cast (будучи, в определенной степени синонимом преобразования типов в стиле C) позволяет легко прострелить себе ногу еще в одном случае:
E>>
E>>char c = ...;
E>>unsigned int u = static_cast< unsigned int >( c );
E>>
E>>В этом легальном C++ коде мы получим проблемы, если тип char будет знаковым, а значение c — отрицательным. И это как раз тот случай, когда "Нельзя = не рекомендуется, т.к. опасно" лично для меня более важно, чем "Нельзя = запрещено спецификацией языка". Но это уже совсем другая история.
АТ>Не совсем понимаю, о каких "проблемах" идет речь. Если значение 'c' отрицательно, то сработает арифметика по модулю, который подчиняются беззнаковые типы, и переменная 'u' проинициализируется значенем 'UINT_MAX + c + 1'. 'static_cast' тут никаким боком совершенно ни при чем. Приведенный тобой код строго эквивалентен простому
АТ>
АТ>unsigned int u = c;
АТ>
АТ>т.е. никакого отношения к 'static_cast' происходящее не имеет вообще. Подводным камнем в этом примере является только зависящая от реализации знаковость или беззнаковость типа 'char'. При чем здесь 'static_cast'?
Просто при том, что большинство компиляторов выдают хотя бы предупреждение на код без static_cast. И если программист, по наивности или забывчивости не заметил, что пытается привести меньшее знаковое число к большему беззнаковому, то у него есть шанс заметить это по предупреждению компилятора. А вот со static_cast у него даже шансов таких не будет. А ведь значение u могло бы использоваться в качестве индекса в каком-нибудь массиве... Но это уже другая тема.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Аноним, Вы писали: А>А немогли бы вы ответить на вопрос в топике (Начнем все сначала. Указатель на функцию) в чем там проблемы.
Проблема в том, что он кода так и не показал...
Я вот со всеми соглашусь.
С одной стороны метод это функция с неявным this, с другой стороны (сёдня с утреца как раз читал статейку с тут про ексепшны в явных/неявных деструкорах наследующих классов) часто возникают сложные моменты на, казалось бы, простяцких ситуациях...
Здравствуйте, korzhik, Вы писали: K>Неожидал что за ночь тут развернётся такая жаркая дискуссия K>Честно говоря удивлён как на ровном месте создалась конфликтная ситуация. K>Не понимаю зачем закрывать тему? K>Вот демагогию заканчивать надо, а обсуждение технических вопросов предлагаю продолжить. K>Вдруг вы заблуждаетесь или мы вас не поняли.
Релакс, это просто тролль. http://www.rsdn.ru/Forum/?mid=965254
один раз в жизни мне такое понадобилось — когда надо было в из программы хардварные бряки на собственные методы делать.
разумеется указатель на обычную функцию. Никаких виртуалов и прочих заморочек — там уже начинается такой compiler specific что даже разбираться желания нету.
I>вообщем привести указатель на функцию к типу void*. I>Компилятор ругается матом. Всякие x_cast<> — тож не помогают. I>Есть ли какое нибудь решенеие.
I>Кстати прочитал Александреску — ничего примечательного
Грязный хак не желаете ли?:
template<typename Class, typename Result, typename Arg0>
void* void_cast(Result (Class::*method)(Arg0))
{
typedef Result (Class::*Method)(Arg0);
union
{
Method m;
void* p;
}u;
u.m = method;
return u.p;
}
//Применение:class A
{
public:
int foo(const char*);
};
void* p = void_cast(&A::foo);
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Irokez, Вы писали:
I>А его реализация Functor это вообще полный ужас. Типа если вам надо вызвать функцию с более 15 парметрами исправте код в моей библиотеке
Системные функции часто имеют много параметров, но чтобы 15 — не могу ни одной припомнить. А самому писать такие функции — ну, можно конечно, только если уверены, что никто потом это не увидит и не узнает.
Не эксперт в бусте, но помоему там можно в лямбда функциях там можно использовать всего 9 параметров (_1,_2,...) — и это более, чем за глаза. Не могу вспомнить случая, чтобы мне потребовалось хотя бы 5.
Здравствуйте, Roman Odaisky, Вы писали:
RO>Здравствуйте, rg45, Вы писали:
R>>Грязный хак не желаете ли?:
RO>А где assert того, что оно туда помещается?
Ассерта нету, потому, что хак грязный
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
--
Справедливость выше закона. А человечность выше справедливости.