> Ведь имя функции само по себе является указателем
Имя функции само по себе указателем не является, но, в целях совместимости с C, любое выражение, обозначающее "обычную" функцию (не функцию-член), может быть неявно приведено к указателю на функцию, но только если будет использовано в соответствующем контексте, требующем такого преобразования. Например, вполне возможно, использование имени функции без преобразования к указателю:
bool foo();
void bar()
{
typedef bool F();
F& f = foo; // ссылка на функцию, имя функции к указателю здесь не приводится
}
Далее, ссылка на функцию может быть неявно приведена к указателю на функцию:
F* pf = f; // указатель на функцию
Но указатель можно получить и явно:
F* pf = &f;
Естественно, все то же самое может быть проделано и с использованием непосредственно имени функции.
Еще раз подчеркиваю, что эта функциональность для "обычных" функций была оставлена только из соображений совместимости с C, т.к. у этого правила есть некоторые отрицательные моменты. Например, вместо:
if (foo())
. . .
можно легко написать:
if (foo)
. . .
что будет корректной с точки зрения языка конструкцией, но вряд ли будет соответствовать ожиданиям программиста. Для этого частного случая большинство (если не все) компиляторов выдаст предупреждение, что сводит реальный вред от этого эффекта, фактически, к нулю.
Однако есть и более сложные аналогичные случаи, которые компиляторы обычно пропускают молча. Например, вместо:
void baz(...);
baz(foo());
можно запросто написать:
void baz(...);
baz(foo);
Естественно, обычной рекомендацией является избегать функций с переменным числом аргументов, т.к. они в C++ тоже оставлены только для совместимости с C, но, так или иначе, проблема существует.
Указатели на функции-члены серьезно отличаются от указателей на "обычные" функции как использованием, так и своим "устройством". Соответственно, т.к. в C указателей на члены не было, то совместимость было обеспечивать не надо, и разработчики C++ смогли позволить себе устранить проблемы неявного приведения функций к указателям на функции хотя бы для функций-членов.
Таким образом, к указателю на функцию-член выражения, обозначающие функцию-член класса, неявно не приводятся. Более того, использования просто имени функции-члена, без явной квалификации идентификатором класса, для получения указателя на член тоже не достаточно. И еще: использование скобок в сочетании с операцией & : &(ClassId::member) — к получению указателя на член тоже не приводит.
Сразу можно заметить, что требование явной квалификации при формировании указателя на член класса позволяет однозначно идентифицировать производимую операцию без анализа контекста. Это не только упрощает написание компилятора, что в данном случае, хотя и весьма желательно, все-таки вторично, но и, что более существенно, значительно облегчает понимание происходящего человеком, читающим код программы.
Чтобы лучше понять обоснованность указанных ограничений, полезным будет также вспомнить, что в C++ есть не только указатели на функции-члены, но и указатели на члены-данные.
Тут же становится очевидной разумность требования явного указания операции &, т.к. синтаксис ClassId::member уже зарезервирован для явной квалификации класса, к переменной-члену которого осуществляется доступ:
struct B
{
int i;
};
struct D : B
{
double i;
D()
{
B::i = 10;
}
};
Также очевидно, что мы хотим, чтобы в контексте класса B выражение &i, как и везде, обозначало получение "обычного" указателя на переменную i. Кроме того, вне класса B, выражение &B::i уже обозначает получение указателя на член B::i.
Поэтому, для того, чтобы отличать в контексте класса B создание указателя на член int B::* от получения "обычного" указателя int*, вполне естественно потребовать всегда, вне зависимости от контекста, явно указывать класс, указатель на член-данные которого мы хотим получить.
Представим теперь, что мы захотели в контексте класса D получить "обычный" указатель int* на переменную B::i. Учитывая значение выражения B::i в контексте класса D, вполне естественным был бы синтаксис &B::i, но он приводит к неоднозначности с операцией формирования указателя на член int B::*. В общем, такое столкновение "интересов" не критично, т.к. потребность получения "обычного" указателя на переменную-член базового класса возникает относительно редко.
Тем не менее, для устранения неоднозначности, было принято решение, что выражение вида &ClassId::member всегда означает формирование указателя на член, а выражение &(ClassId::member) — получение "обычного" указателя. Последнее для нестатических переменных-членов, в принципе, эквивалентно выражению &this->ClassId::member.
Возвращаясь к указателям на функции-члены, вряд ли было бы удобным, если бы cемантика применения к функциям-членам операции & отличалось от аналогичных действий с членами-данными.
Кроме того, для обеспечения возможности компиляции шаблонов, из-за схожести синтаксиса, фактически необходимо на получение указателей на функции-члены наложить те же ограничения, что и на получение указателей на члены-данные.
Posted via RSDN NNTP Server 1.9 gamma
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Undead,
> Спасибо. Теперь, конечно, буду следить за &. Все больше не люблю VC 7.1
Не надо так категорично... К сожалению, в реальной жизни у большинства разработчиков компиляторов есть множество забот помимо буквального следования стандарту. Плюс, все еще дополнительно осложняется тем, что многие компиляторы имеют ощутимую "достандартную" историю, и их разработчики обычно вынуждены обеспечивать обратную совместимость со старым кодом. И в этом отношении разработчики VC++ заслуживают самого хорошего отношения. Впрочем, как раз об этом совсем недавно уже писалось
И хотя я совершенно согласен, что подобное поведение со стороны VC++ 7.1 в данном конкретном случае может приводить (и приводит) к некоторым проблемам, в частности, и описанным выше, стоит взглянуть на проблемы неукоснительного соблюдения стандарту с чуть более общей точки зрения.
Прежде всего следует заметить, что с точки зрения эволюции языка наличие расширений в конкретных реализациях является желаетельной вещью. Фактически, это наиболее хорошо работающий механизм внесения изменений и дополнений в язык.
К сожалению, несмотря на высокую квалификацию и лучшие намерения участников процесса стандартизации, как показала практика, попытки внесения изменений в язык непосредственно в ходе составления стандарта далеко не всегда приводили к лучшим результатам. Пожалуй, самыми яркими примерами являются экспорт шаблонов и снабжение функций спецификациями исключений.
И это вполне понятно, так как, как бы хорошо ни выглядели те или иные возможности "на бумаге" и в "лабораторном" тестировании, без реального опыта их реализации и использования очень сложно, если вообще возможно, выяснить многие проблемы с ними связанные, равно как и оценить реальную их пользу.
Неудачно спроектированные возможности, попавшие в стандарт, обходятся дорого всему сообществу C++: и комитету, потратившему ресурсы на разработку спецификации, и разработчикам компиляторов, потратившим ресурсы на реализацию этих возможностей в своих продуктах, и пользователям, которым впоследствии приходится с ними работать.
И, к сожалению, из-за возможности относительно широкого использования даже неудачных возможностей, убирать их из языка или менять семантику соответствующих языковых конструкций совсем не просто.
Также стоит учитывать, что стандарт физически не может охватить все возможные области использования языка и, соответственно, не может включать в себя все средства, нужные пользователям конкретных компиляторов для практической работы. Напротив, в стандарт должны попадать только те моменты, которые описывают общее подмножество языка, предназначенное для реализации всеми разработчиками компиляторов.
Кстати, о пользователях... Вполне очевидно, что наиболее близкими к конечным пользователям являются именно разработчики компиляторов. Соответственно, учитывая проблемы изобретения языковых возможностей в ходе стандартизации, фактически, общим местом является то, что комитет в целом не должен заниматься работой по проектированию языка, а только уточнять и согласовывать решения, предлагаемые "со стороны", часто разработчиками тех или иных реализаций.
Конечно, разделение на комитет и "других" не вполне четко, т.к. большинство, если не все, производители компиляторов представлены в комитете стандартизации. И тем не менее, есть существенная практическая разница в проектировании тех или иных возможностей непосредственно в процессе стандартизации, и проектированием этих новых возможностей в процессе разработки компилятора.
Во втором случае разработчики имеют много возможностей для практических исследований как в ходе разработки, так и при использовании "внутренних" версий компилятора (вполне естественно, что многие разработчики компиляторов используют свои же продукты для своих внутренних разработок). Далее, после всестороннего внутреннего изучения, разработчики компилятора имеют много возможностей для поэтапного опробования новых возможностей в версиях, поставляемых пользователям.
В результате такого подхода неизбежные ошибки проектирования новых возможностей в целом сообществу C++ обходятся намного дешевле, нежели если они попадут в стандарт, так как если "фича" окажется неудачной, пользователи других компиляторов не будут затронуты, да и разработчики компилятора вполне могут обнаружить проблемы на ранних этапах внедрения, и предотвратить ее широкое использование, т.к. имеют больше возможностей для оперативного взаимодействия с конечными пользователями. Конечно, не всегда удаление неудачных расширений происходит так хорошо, и они запросто могут оставаться в компиляторах надолго, но это все равно намного лучше, чем если бы они попали в стандарт, и их пришлось проводить во все реализации.
Так или иначе, итогом не всегда гладкого пути разработки первого стандарта C++ стало очень консервативное отношение комитета к внесению изменений непосредственно в стандарт, минуя "полевые испытания", скажем, в виде расширений компиляторов.
Возвращаясь к нашим баранам, т.е. к неявному преобразованию квалифицированного имени функции-члена к указателю на член, естественно, важно проводить границу между расширениями языка и несоответствием стандарту. Этот момент вполне четко в стандарте оговорен (1.4):
— If a program contains no violations of the rules in this International Standard, a conforming implementation shall, within its resource limits, accept and correctly execute that program.
— If a program contains a violation of any diagnosable rule, a conforming implementation shall issue at
least one diagnostic message <...>
A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this International Standard. Having done so, however, they can compile and execute such programs.
Соответственно, данное поведение VC++ корректной реализацией расширения не является, т.к. требуемого предупреждения компилятор не выдает.
Судя по всему, эта функциональность существует в VC++ по историческим причинам, для совместимости со старым кодом, с тех "славных" времен, когда стандарта еще не было, и разработчики компиляторов вынуждены были гадать о точном определении языка, и, естественно, периодически их трактовка отличалась от формулировок, позднее внесенных в стандарт.
Чуть выше меня снова занесло в общие категории... Но, думаю, мысль должна быть вполне ясна: уж очень сильно требовать от разработчиков педантичного следования стандарту в диагностике всех нарушений, пожалуй, не стоит, пока, естественно, их компилятор корректно компилирует корректные программы. Хотя в данном случае какая-нибудь прагма и/или опция компилятора, выключающая это безобразие, была бы очень кстати.
Posted via RSDN NNTP Server 1.9 gamma
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Шахтер,
> U>Спасибо. Теперь, конечно, буду следить за &. > U>Все больше не люблю VC 7.1 > > Включи /Za.
Забавно, что /Za работает для безобидного случая отсутствия & при явном присваивании указателю на член, но не приводит к исправлению поведения, например, в таком случае, порождающем реальные проблемы:
class C
{
public:
int f() { return 0; }
};
void foo(...) { }
int main()
{
foo(C::f);
}
Posted via RSDN NNTP Server 1.9 gamma
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
и почему везде используется вторая? Ведь имя функции само по себе является указателем, да и код в первом случае лучше читается.
Я компилил только на VC 7.1
Здравствуйте, Undead, Вы писали:
U>Не подскажете, чем конструкция U>
U>mem_fun( SomeClass::SomeFunction )
U>
U> отличается от U>
U>mem_fun( &SomeClass::SomeFunction )
U>
U> и почему везде используется вторая? Ведь имя функции само по себе является указателем, да и код в первом случае лучше читается. U>Я компилил только на VC 7.1
Первая не корректна (согласно стандарту), вторая корректна. Почему первая обрабатываеться корректно VSC++ я вообще не понимаю если честно.
Здравствуйте, korzhik, Вы писали:
K>хм, так причём здесь 7.1? K>Это требование стандарта
Я к тому, что он это пропускает.
Он вообще многое пропускает, что по стандарту не разрешено.
А еще меня в свое время убило, что map::erase возвращает итератор, но это уже, наверное, P.J. Plauger'у спасибо.
Здравствуйте, Шахтер, Вы писали:
Ш>Здравствуйте, Undead, Вы писали:
U>>Спасибо. Теперь, конечно, буду следить за &. U>>Все больше не люблю VC 7.1
Ш>Включи /Za.
Здравствуйте, Undead, Вы писали:
U>Здравствуйте, Шахтер, Вы писали:
Ш>>Здравствуйте, Undead, Вы писали:
U>>>Спасибо. Теперь, конечно, буду следить за &. U>>>Все больше не люблю VC 7.1
Ш>>Включи /Za.
U>У меня на этом все "WinAPI" к черту слетает.
Есть такое. Майкрософт всё никак не перепишет windows.h в соответствии со стандартом.