C ++, эквивалентность между указателями на функции и указателями на функции-члены? - PullRequest
4 голосов
/ 04 января 2009

Я привык думать о функциях-членах как об особом случае обычных функций, где функции-члены имеют дополнительный параметр в начале списка параметров для указателя 'this', то есть объект, на котором функция-член должна действовать. В прошлом я использовал метод boost :: function и никогда не сталкивался с какими-либо проблемами:

boost::function f<(void)(MyObject*, int, int)> = &MyObject::method_that_takes_two_ints;

Но я видел этот синтаксис для указателей на функции-члены:

void (MyObject::*f)( int, int ) = &MyObject::method_that_takes_two_ints;

В этом синтаксисе параметр this не отображается. Это заставило меня задуматься о том, являются ли функции указателя на член на самом деле отдельным зверем, и это повышение заботилось о деталях для меня.

Что стандарт предписывает размещать параметр this? Возможно, только на моем компиляторе дополнительный аргумент 'this' стоит первым, а на других компиляторах он может быть в конце? Мне просто повезло, что мое мышление согласуется с тем, как мои компиляторы (GCC4, VS2005) справляются с этим? Являются ли указатели на функции-члены всегда частным случаем указателей на функции с дополнительным параметром или компилятор может реализовывать их по-разному?

Ответы [ 9 ]

10 голосов
/ 04 января 2009

Стандарт почти ничего не говорит о том, где должен быть расположен указатель this, и на самом деле довольно часто используется другое соглашение о вызовах для функций-членов. (Таким образом, указатель 'this' - это не просто дополнительный первый аргумент, он на самом деле хранится не в том месте, где обычно находится первый аргумент)

В частности, MSVC использует thiscall соглашение о вызовах для функций-членов и stdcall в других местах. http://www.hackcraft.net/cpp/MSCallingConventions/#thiscall описывает различия между ними, но обратите внимание, что thiscall хранит указатель this в регистре ECX, тогда как stdcall сохраняет все параметры в стеке.

Вам определенно лучше относиться к ним как к совершенно другим типам. Указатель на функцию-член , а не , это просто указатель на функцию с дополнительным параметром.

9 голосов
/ 04 января 2009

Указатель this не сохраняется вдоль указателя на член (указатели на функции-члены являются частным случаем этого). Если вы просто делаете

void (MyObject::*f)( int, int ) = &MyObject::method_that_takes_two_ints;

тогда сохраняется только информация о том, какую функцию-член следует вызывать в объекте, который вы позднее должны будете предоставить. Если вы хотите вызвать его, вы должны передать объект, откуда компилятор получит указатель this.

MyObject o; (o.*f)(1, 2);

Указатель на функцию-член - это просто указатель на член, тип которого (на который указывают) является типом функции. Стандарт гласит, что указатели на функции-члены не имеют своего собственного «типа функции-члена», на который они указывают, и который каким-то образом включает тип указателя this.

int main() {
    typedef void fun() const;
    fun MyObject::*mem_function_ptr = 
        &MyObject::const_method_that_takes_two_ints;
}

fun в этом коде является типом функции. Тип, который имеет «нормальная» функция. Указатель на функцию, в отличие от указателя на функцию-член, является просто указателем на функцию такого типа:

void foo() { cout << "hello"; }
int main() {
    typedef void fun();
    fun * f = &foo;
}

Хотя указатель на функцию-член имеет дополнительный уровень указателя на член поверх этого типа функции.

Что-то о указателе this и о том, как он относится к объекту, на который он указывает (не технический, а только теоретический материал):

Каждая функция-член имеет скрытый параметр, называемый implicit object parameter, который имеет тип MyObject& или MyObject const& в зависимости от того, есть ли у вас функция-член const или nonstst. o Объект, для которого вы вызываете функцию-член, - это implied object argument, который передается параметру. В теории стандарта, составляющей правила, которые описывают, как вызываются функции-члены, неявный параметр объекта является первым скрытым параметром. Это концептуально и не означает, что это реальный случай в реализациях. Затем подразумеваемый объектный аргумент привязывается к этому неявному объектному параметру, возможно, вызывая неявные преобразования (поэтому, если вы вызываете функцию-член const для неконстантного объекта, квалификационное преобразование преобразуется из MyObject в MyObject const&. неконстантные функции - лучший выбор, чем константные функции для вызова неконстантных объектов). Например, в этом коде можно сказать:

struct A {
    operator int() const { return 0; }
};

int main() { 
    A a;
    int i = a; // implicit conversion using the conversion function
}

что подразумеваемый аргумент объекта a типа A связан с неявным параметром объекта типа A const&, на объект которого затем указывает указатель this, имеющий здесь тип A const*. Важно отметить, что неявный параметр объекта является только теоретической конструкцией, чтобы формализовать, как составлены правила для вызова функции-члена (и конструкторы не включают их), в то время как указатель this фактически существует. this является указателем, потому что когда был введен this, C ++ еще не имел ссылок.

Надеюсь, это поможет вам понять этот вопрос.

7 голосов
/ 05 января 2009

Отличная статья об указателях на функции-члены - это Указатели на функции-члены CodeProject и максимально быстрые делегаты C ++ . В этой статье описываются указатели на функции-члены от простых случаев до виртуальных указателей на функции-члены с множественным наследованием. В качестве бонуса он обеспечивает реализацию делегатов, которая может быть действительно полезной.

3 голосов
/ 05 января 2009

Обратите внимание, что размер указателя на функцию-член может отличаться при использовании разных компиляторов.

Еще одна вещь, которую стоит отметить, как написано в The Old New Thing blog :

Размер указатель на член-функцию может измениться в зависимости от класса.

3 голосов
/ 04 января 2009

Да, указатели на функции и указатели на участников - совершенно разные звери. Указателям на члены необходимо дать экземпляр объекта для разыменования с помощью операторов ->* или .*. При создании указателя на элемент параметр this не используется, поскольку this определяется при использовании указателя на элемент (объект слева от ->* или .*).

Обратите внимание, что между функцией указатель на член и переменной указатель на член, вероятно, меньше различие, чем между функцией указатель на член и указатель на обычную функцию.

Как правило, функции-члены и обычные функции могут иметь совершенно разные соглашения о вызовах, поэтому вы не можете приводить между ними.

1 голос
/ 05 января 2009

Чтобы ответить на все вопросы: Да, это специальные указатели, отличающиеся от обычных указателей. Да, boost :: function распознает их.

Стандарт ничего не говорит о внутренних деталях стеков вызовов. Фактически, многие компиляторы могут использовать целочисленные регистры, регистры с плавающей точкой и / или стек в зависимости от фактического списка аргументов. Указатель 'this' - это еще один особый случай.

Boost :: function решает эту проблему, используя два пути кода внутри. Вы можете убедиться в этом, проверив стек вызовов для двух случаев. Если ваша функция boost :: function хранит указатель на функцию-член, оператор () разделит список аргументов. Первый аргумент используется как объект, для которого вызывается функция-член с остальными аргументами.

1 голос
/ 04 января 2009

Это определенно разные типы, и любые ваши предположения будут зависеть от платформы / компилятора.

Эта страница содержит больше информации о реализации точек функций-членов, чем я когда-либо хотел знать, включая подробности реализации для многочисленных популярных компиляторов.

0 голосов
/ 05 января 2009

Я думаю, вы можете найти эту ссылку довольно интересной:

http://www.parashift.com/c++-faq-lite/pointers-to-members.html

Это очень хорошее описание всего, что вы хотели бы знать о указателях на члены.

0 голосов
/ 05 января 2009

Чтобы дополнить ответ каждого, Boost.Function работает, специализируя оператор присваивания на указателях на функции-члены, чтобы он мог определять, когда вы его передали. Когда вы вызываете эту функцию, она будет внутренне интерпретировать ее для правильного метода вызова указателя на функцию-член ((obj->*fun)(args)).

...