Почему в C ++ нет указателя на тип функции-члена? - PullRequest
11 голосов
/ 20 января 2009

Я могу быть совершенно не прав, но, насколько я понимаю, C ++ на самом деле не имеет собственного типа «указатель на функцию-член». Я знаю, что вы можете делать трюки с Boost, mem_fun и т. Д. Но почему разработчики C ++ решили не иметь 64-битный указатель, содержащий указатель на функцию и указатель на объект, например?

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

То, что я не имею в виду, это указатель на общую функцию-член класса. Е.Г.

int (Fred::*)(char,float)

Это было бы так полезно и сделало бы мою жизнь проще.

Hugo

Ответы [ 9 ]

17 голосов
/ 20 января 2009

@ RocketMagnet - это ответ на ваш другой вопрос , который был назван дубликатом. Я отвечаю на этот вопрос, а не на этот.

Как правило, указатель C ++ на функции-члены нельзя переносить через иерархию классов. Тем не менее, вы часто можете сойти с рук. Например:

#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

$

Итак, чтобы ответить "почему в C ++ нет указателя на член-функцию-на-пустом-классе?" в том, что у этого воображаемого базового класса всего нет членов, так что нет никакого значения, которое вы могли бы ему приписать! «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);
}

Так что на ваш вопрос. Почему C ++ не поддерживает такой тип приведения. Типы указателя на функцию-член уже имеют дело с виртуальными против не виртуальных базовых классов и виртуальными против не виртуальных функций-членов, все в одном типе, увеличивая их до 4 * sizeof (void *) на некоторых платформах. Я думаю, потому что это еще больше усложнит реализацию указателя на функцию-член, а необработанные указатели на функции уже так хорошо решают эту проблему.

Как прокомментировали другие, C ++ предоставляет авторам библиотек достаточно инструментов, чтобы сделать это, и тогда «нормальным» программистам, таким как вы и я, следует использовать эти библиотеки вместо того, чтобы потворствовать этим деталям.

РЕДАКТИРОВАТЬ: отмеченные сообщества вики. Пожалуйста, отредактируйте, чтобы включить соответствующие ссылки на стандарт C ++, и добавьте курсивом. (особенно добавить ссылки на стандарт, где мое понимание было неправильным! ^ _ ^)

12 голосов
/ 20 января 2009

Как отмечали другие, C ++ имеет тип указателя на функцию-член.

Вы искали термин «связанная функция». Причина, по которой C ++ не предоставляет синтаксического сахара для привязки функций, заключается в философии, заключающейся в том, что она предоставляет только самые базовые инструменты, с помощью которых вы можете создавать все, что хотите. Это помогает сохранить язык «маленьким» (или, по крайней мере, менее огромным).

Аналогично, C ++ не имеет примитива lock {}, как у C #, но имеет RAII, который используется в scoped_lock boost.

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

4 голосов
/ 20 января 2009

Я думаю, что вы ищете в этих библиотеках ...

Быстрые делегаты 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

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

Да.

Например,

int (Fred::*)(char,float)

- указатель на функцию-член класса Fred, которая возвращает int и принимает char и float.

2 голосов
/ 22 января 2009

Я думаю, что ответ заключается в том, что разработчики C ++ предпочитают не иметь в языке вещей, которые можно было бы так же легко реализовать в библиотеке. Ваше собственное описание того, что вы хотите, дает совершенно разумный способ его реализации.

Я знаю, это звучит забавно, но C ++ - это минималистичный язык. Они оставляли библиотекам все, что могли.

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

Проблема, конечно же, не в том, чтобы иметь указатель объекта и указатель на функцию в одном простом в использовании пакете, потому что вы могли бы сделать это, используя указатель и блок. (Такие блоки управления уже используются в VC ++ на платформе x86 для поддержки указателей на виртуальные функции-члены, так что эти указатели занимают всего 4 байта.) Возможно, у вас может быть много блоков обработки, это правда, но люди уже полагаются на компоновщик устраните дублирующиеся экземпляры шаблонов - я делаю, во всяком случае - и есть только очень много vtable, и это смещения, которые вы в конечном итоге получите на практике. Скорее всего, накладные расходы не будут значительными для любой программы разумного размера, и если вы не используете этот материал, то это вам ничего не будет стоить.

(Архитектуры, которые традиционно используют TOC, будут хранить указатель TOC в части указателя функции, а не в блоке, как они уже должны были это сделать.)

(Этот новый тип объекта не может быть точно заменен обычным указателем на функцию, конечно, потому что размер будет другим. Однако они будут записаны одинаково в точке вызова.)

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

Это, вероятно, не имеет большого значения для x86, по крайней мере, с этим вызовом, потому что вы можете просто загрузить ECX независимо от того, что и если вызывающая функция не нуждается в этом, то это будет поддельным. (И я думаю, что VC ++ предполагает, что ECX в данном случае фальшивый в любом случае.) Но на архитектурах, которые передают аргументы для именованных параметров в регистрах функциям, вы можете получить достаточное количество перемешиваний в thunk, и если аргументы стека будут сдвинуты влево -Правильно, тогда вы в основном чучела. И это не может быть исправлено статически, потому что в пределе нет информации о единицах перекрестного перевода.

[Редактировать: MSalters, в комментарии к посту ракетного магнита выше, указывает, что если известны и объект, и функция, то смещение this и т. Д. Могут быть определены немедленно. Это совершенно не происходило со мной! Но, учитывая это, я предполагаю, что нужно хранить только точный указатель объекта, возможно смещение, и точный указатель функции. Я думаю, это делает ненужные тухки совершенно ненужными, но я уверен, что проблемы с указанием на функции-члены и функции, не являющиеся членами, останутся.]

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

TR1 имеет функцию std :: tr1 ::, и она будет добавлена ​​в C ++ 0x. Так что в некотором смысле это действительно так.

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

Причина, по которой делегаты тяжелы, заключается в том, что указатель метода часто больше обычного указателя. Это происходит всякий раз, когда метод является виртуальным. Указатель на метод будет вызывать другую функцию в зависимости от того, какой базовый класс ее использует. Для этого требуется как минимум два указателя, vtable и offset. Есть еще одна странность, связанная с методом, из класса, участвующего в множественном наследовании.

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

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

C ++ уже большой язык, и добавление этого сделало бы его больше. То, что вы действительно хотите, даже хуже, чем просто связанная функция-член, это что-то ближе к boost :: function. Вы хотите сохранить как void(*)(), так и пару для обратных вызовов. В конце концов, причина в том, что вы хотите, чтобы вызывающий абонент получил полный обратный вызов, и вызываемый абонент не должен заботиться о точных деталях.

Размер, вероятно, будет sizeof(void*)+sizeof(void(*)()). Указатели на функции-члены могут быть больше, но это потому, что они не связаны. Им нужно иметь дело с возможностью того, что вы, например, берете адрес виртуальной функции. Тем не менее, встроенный тип привязки указателя к члену-функции не будет страдать от этих издержек. Он может разрешить точную функцию, которая будет вызвана в момент привязки.

Это невозможно с UDT. boost :: function не может отбросить накладные расходы PTMF, когда он связывает указатель объекта. Вы должны знать, понимать структуру PTMF, vtable, и так далее - все это нестандартные вещи. Тем не менее, мы могли бы получить это сейчас с C ++ 1x. Как только он появится в std ::, он станет честной игрой для поставщиков компиляторов. Сама реализация стандартной библиотеки не переносима (см., Например, type_info).

Полагаю, вы все равно хотели бы иметь хороший синтаксис, даже если поставщик компиляторов реализует это в библиотеке. Я бы хотел std::function<void(*)()> foo = &myX && X::bar. (Это не противоречит существующему синтаксису, поскольку X :: bar не является выражением - только & X :: bar) *

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

Мне приходит в голову, что у методов есть неявный аргумент this, поэтому указатель c на метод недостаточен для вызова метода (потому что нет способа определить, какой экземпляр следует использовать для this (или даже если любой экземпляр в настоящее время существует)).

Редактировать: Ракетный магнит комментирует, что он обратился к этому вопросу, и, похоже, это так, хотя я думаю, что это было добавлено после того, как я начал этот ответ. но я все равно скажу "моя вина".

Итак, позвольте мне немного углубиться в эту мысль.

C ++ тесно связан с c и имеет все свои внутренние типы, совместимые с более ранним языком (я полагаю, в основном из-за истории развития c++). Таким образом, внутренний указатель c++ является указателем c и не способен поддерживать запрашиваемое вами использование.

Конечно, вы могли бы создать производный тип для выполнения работы - как в реализации boost - но такой критерий принадлежит библиотеке.

...