Почему у С++ нет указателя на тип функции-члена?

Я мог быть совершенно неправ здесь, но, как я понимаю, С++ на самом деле не имеет собственного типа "указатель на функцию члена". Я знаю, что вы можете делать трюки с 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.

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

Ответ 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, но такой критерий принадлежит библиотеке.