Undead,

> mem_fun( SomeClass::SomeFunction )

> Ведь имя функции само по себе является указателем

  • Имя функции само по себе указателем не является, но, в целях совместимости с 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
Автор: Павел Кузнецов    Оценить