Почему у С++ нет указателя на тип функции-члена?
Я мог быть совершенно неправ здесь, но, как я понимаю, С++ на самом деле не имеет собственного типа "указатель на функцию члена". Я знаю, что вы можете делать трюки с Boost и mem_fun и т.д. Но почему дизайнеры С++ решили не иметь 64-битного указателя, содержащего указатель на функцию и указатель на объект, например?
То, что я имею в виду конкретно, является указателем на функцию-член определенного объекта неизвестного типа. И.Е. что вы можете использовать для обратного вызова. Это будет тип, который содержит два значения. Первое значение является указателем на функцию, а второе значение является указателем на конкретный экземпляр объекта.
То, что я не имею в виду, является указателем на общую функцию-член класса. Например.
int (Fred::*)(char,float)
Это было бы так полезно и облегчило бы мою жизнь.
Уго
Ответы
Ответ 1
@RocketMagnet - это ответ на ваш другой вопрос, который был помечен как дубликат. Я отвечаю на этот вопрос, а не на этот.
В общем, указатель С++ на функции-члены нельзя переносить в иерархии классов. Это говорит, что вы часто можете избежать неприятностей. Например:
#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};
int main() {
typedef void (A::*pmf_t)();
C c; c.x = 42; c.y = -1;
pmf_t mf = static_cast<pmf_t>(&C::foo);
(c.*mf)();
}
Скомпилируйте этот код, и компилятор правильно жалуется:
$ cl /EHsc /Zi /nologo pmf.cpp
pmf.cpp
pmf.cpp(15) : warning C4407: cast between different pointer to member representations, compiler may generate incorrect code
$
Итак, чтобы ответить "почему С++ не имеет указателя на элемент-функцию-на-void-классе?" заключается в том, что этот мнимый базовый класс не имеет членов, поэтому нет никакой ценности, которую вы могли бы смело назначить ему! "void (C::)()" и "void (void::)()" являются взаимно несовместимыми типами.
Теперь, держу пари, вы думаете: "Подожди, я просто бросил указатели-члены-функции раньше!" Да, вы можете иметь, используя reinterpret_cast и одно наследование. Это в той же категории других реинтерпретов:
#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};
class D { public: int z; };
int main() {
C c; c.x = 42; c.y = -1;
// this will print -1
D& d = reinterpret_cast<D&>(c);
cout << "d.z == " << d.z << "\n";
}
Итак, если void (void::*)()
действительно существует, но вы ничего не можете безопасно/переносить на него.
Традиционно вы используете функции подписи void (*)(void*)
в любом месте, где бы вы ни использовали void (void::*)()
, потому что, в то время как указатели-функции-члены не отливы вверх и вниз по иерархии наследования, указатели void действительно отливают. Вместо этого:
#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};
void do_foo(void* ptrToC){
C* c = static_cast<C*>(ptrToC);
c->foo();
}
int main() {
typedef void (*pf_t)(void*);
C c; c.x = 42; c.y = -1;
pf_t f = do_foo;
f(&c);
}
Так на ваш вопрос. Почему С++ не поддерживает такой тип кастинга. В классах-указателях-участниках уже приходится иметь дело с виртуальными виртуальными базовыми классами, а виртуальные и не виртуальные функции-члены все одинаковы, раздувая их до 4 * sizeof (void *) на некоторых платформах. Я думаю, потому что это еще больше усложнит реализацию функции указатель-член, а необработанные указатели функций уже так хорошо решают эту проблему.
Как и другие комментируют, С++ дает библиотечным писателям достаточно инструментов, чтобы это сделать, а затем "нормальные" программисты, такие как вы и я, должны использовать эти библиотеки, а не вспотели этими деталями.
EDIT: отмеченная вики сообщества. Пожалуйста, только отредактируйте, чтобы включить соответствующие ссылки на стандарт С++ и добавить курсивом. (особенно ссылки на стандарт, где мое понимание было неправильным! ^ _ ^)
Ответ 2
Как указывали другие, С++ имеет тип указателя функции-члена.
Термин, который вы искали, - это "связанная функция". Причина, по которой С++ не дает синтаксического сахара для привязки функций, объясняется его философией предоставления только самых основных инструментов, с помощью которых вы можете построить все, что хотите. Это помогает удерживать язык "маленьким" (или, по крайней мере, меньше ума, невероятно огромным).
Аналогично, С++ не имеет примитива блокировки {}, такого как С#, но имеет RAII, который используется boost scoped_lock.
Конечно, есть школа мысли, в которой говорится, что вы должны добавить синтаксический сахар для всего, что может быть полезно. К лучшему или худшему, С++ не относится к этой школе.
Ответ 3
Я думаю, что то, что вы ищете, может быть в этих librairies...
Быстрые делегаты http://www.codeproject.com/KB/cpp/FastDelegate.aspx
Boost.Function http://www.boost.org/doc/libs/1_37_0/doc/html/function.html
И вот очень подробное объяснение связанных с указателем функций вопросов http://www.parashift.com/c++-faq-lite/pointers-to-members.html
Ответ 4
Он делает.
Например,
int (Fred::*)(char,float)
является указателем на функцию-член класса Fred
, которая возвращает int
и принимает char
и float
.
Ответ 5
Я думаю, что ответ заключается в том, что разработчики С++ предпочитают не иметь на языке того, что можно так же легко реализовать в библиотеке. Ваше собственное описание того, что вы хотите, дает совершенно разумный способ его реализации.
Я знаю, это звучит смешно, но С++ - это минималистический язык. Они оставляли в библиотеках все, что могли им оставить.
Ответ 6
TR1 имеет std:: tr1:: function, и он будет добавлен в С++ 0x. Таким образом, в некотором смысле он имеет это.
Одна из концепций дизайна С++ заключается в следующем: вы не платите за то, что не используете. Проблема с deltaates стиля С# заключается в том, что они тяжелы и требуют языковой поддержки, что каждый будет платить за то, использовали ли они их или нет. Вот почему реализация библиотеки предпочтительнее.
Причина, по которой делегаты тяжелы, заключается в том, что указатель метода часто больше обычного указателя. Это происходит всякий раз, когда метод является виртуальным. Указатель метода вызовет другую функцию в зависимости от того, какой базовый класс использует ее. Для этого требуется по крайней мере два указателя, vtable и смещение. Существует другая странность, связанная с этим методом, из класса, связанного с множественным наследованием.
Все, что сказал, я не писатель-компилятор. Возможно, было возможно создать новый тип для указателей связанных меток, которые подорвут виртуальность упомянутого метода (ведь мы знаем, что такое базовый класс, если метод связан).
Ответ 7
Проблема заключается не в основах наличия указателя на объект и указателя функции в одном удобном в использовании пакете, потому что вы можете сделать это с помощью указателя и thunk. (Такие thunks уже используются VС++ на x86 для поддержки указателей на виртуальные функции-члены, так что эти указатели занимают всего 4 байта.) Возможно, у вас может получиться много thunks, это правда, но люди уже полагаются на компоновщик на исключить дублирование экземпляров шаблонов - я все равно - и там будет только так много vtable, и это приведет к тому, что вы на практике закончите. Накладные расходы, вероятно, не были бы значительными для любой программы с разумным размером, и если вы не будете использовать этот материал, это ничего не будет стоить вам.
(Архитектуры, которые традиционно используют TOC, будут хранить указатель TOC в части указателя функции, а не в thunk, как и они должны были бы уже делать.)
(Этот новый тип объекта не будет точно подставляться для нормального указателя на функцию, конечно, потому что размер будет другим, но они будут записаны одинаково в точке вызова.)
Проблема, которую я вижу, - это вызов вызывающего соглашения: поддержка указателей на функции таким образом может быть сложной в общем случае, потому что сгенерированный код должен будет подготовить аргументы (включенные) таким же образом, независимо от того, фактический тип вещи, функции или функции-члена, на которые указывает указатель.
Это, вероятно, не очень важно для x86, по крайней мере, не с thiscall, потому что вы можете просто загрузить ECX независимо и принять, что если вызывающая функция не нуждается в этом, тогда она будет фиктивной. (И я думаю, что VС++ предполагает, что ECX в этом случае является фиктивным.) Но на архитектурах, которые передают аргументы для именованных параметров в регистры для функций, вы можете в итоге получить достаточное количество перетасовки в thunk, и если аргументы стека будут нажаты влево -то-право, тогда вы в основном набиты. И это не может быть зафиксировано статически, потому что в пределе нет информации о перекрестном переводе.
[Edit: MSalters, в комментарии к сообщению о ракетном магнетике выше, указывает, что, если известны как объект AND, так и это смещение и т.д., можно сразу определить. Это совершенно не произошло со мной! Но, имея в виду это, я полагаю, что нужно хранить только точный указатель объекта, возможно, смещение и точный указатель на функцию. Это делает ненужные трюки - я думаю, - но я уверен, что вопросы указания функций-членов и не-членных функций останутся.]
Ответ 8
С++ - это уже большой язык, и добавление этого сделало бы его больше. То, что вы действительно хотите, еще хуже, чем функция связанного члена, это нечто более близкое к boost:: function. Вы хотите сохранить как void(*)()
, так и пару для обратных вызовов. В конце концов, причина в том, что вы хотите, чтобы вызывающий вызывал полный обратный вызов, а вызываемый не должен заботиться о точных деталях.
Размер будет, вероятно, sizeof(void*)+sizeof(void(*)())
. Указатели на функции-члены могут быть больше, но это потому, что они не связаны. Им нужно иметь дело с возможностью того, что вы, например, берете адрес виртуальной функции. Однако встроенный тип bound-pointer-to-member-function не будет страдать от этих накладных расходов. Он может разрешить точную функцию, которая будет вызываться в момент привязки.
Это невозможно с помощью UDT. Функция boost:: не может отменить накладные расходы PTMF при привязке указателя объекта. Вам необходимо знать структуру PTMF, vtable и т.д. - все нестандартные вещи. Тем не менее, мы можем получить там теперь с С++ 1x. Как только это будет в std::, это справедливая игра для поставщиков компиляторов. Сама реализация стандартной библиотеки не переносима (см., Например, type_info).
Вы все равно хотите иметь хороший синтаксис, я думаю, даже если поставщик компилятора реализует это в библиотеке. Мне бы хотелось std::function<void(*)()> foo = &myX && X::bar
. (Он не сталкивается с существующим синтаксисом, поскольку X:: bar не является выражением - only & X:: bar)
Ответ 9
Мне кажется, что методы имеют неявный аргумент this
, поэтому указатель c
для метода недостаточно для того, чтобы вызвать метод (потому что нет способа определить, какой экземпляр следует использовать для this
(или даже если какой-либо экземпляр в настоящее время существует)).
Изменить: Комментарии Rocketmagnet, которые он затронул в вопросе, и, похоже, это так, хотя я думаю, что это было добавлено после того, как я начал этот ответ. но я скажу "mea culpa", во всяком случае.
Итак, позвольте мне немного подумать над этой мыслью.
С++ тесно связан с c и имеет все его внутренние типы, совместимые с более ранним языком (в основном из-за истории развития c++
, я полагаю). Таким образом, встроенный указатель c++
является указателем c
и не может поддерживать использование, которое вы запрашиваете.
Конечно, вы могли бы построить производный тип для выполнения задания --- как в реализации boost, но такой критерий принадлежит библиотеке.