Вызов указателя функции-члена на интеллектуальный указатель

Имея дело с указателями функций-членов класса, мы можем вызывать функцию для экземпляра объекта со следующим синтаксисом:

struct X {
    void foo();
};

X x;                // Instance
auto f = &X::foo;   // Member function pointer

(x.*f)();           // Call function f on x

При наличии необработанного указателя экземпляра синтаксис таков:

X *xptr = new X();

(xptr->*f)();

Это прекрасно следует аналогии со статическим вызовом функции:

x.foo();
x->foo();

Однако, когда у меня есть интеллектуальный указатель класса, это не работает:

unique_ptr<X> xsptr(new X());

(xsptr->*f)();    // g++: error: no match for 'operator->*' in 'xsptr ->* f'

Я обхожу эту проблему, сначала применяя оператор разыменования, а затем вызывая с синтаксисом .:

((*xsptr).*f)();

Насколько это некрасиво?

Предполагается ли, что компилятор отвергает стрелочный синтаксис сверху? Потому что обычно (при статическом вызове функции) он вызывает operator ->. Разве ->* не должен также просто позвонить operator ->?

Ошибка компилятора частично отвечает на этот вопрос: ошибка гласит, что мы можем перегрузить operator ->* для вызова указателя функции-члена на разыменованный объект, чего unique_ptr не делает. Но это вызывает больше вопросов. Почему бы умным указателям этого не делать, если этого требует язык? Предполагается ли, что не это делается, потому что это создает другие проблемы? И почему мы вообще должны писать этот оператор вместо неявного вызова указателя функции-члена на объект, возвращаемый ->? (Поскольку это поведение при написании xsptr->foo())


person leemes    schedule 25.03.2013    source источник
comment
Почему ->* для значения, не являющегося указателем, должно что-то делать? Компилятор не знает, что unique_ptr пытается имитировать необработанный указатель. Однако operator->* можно перегрузить, но его чистая реализация раздражает.   -  person Xeo    schedule 25.03.2013
comment
@Xeo Я имею в виду, что стандарт может хорошо определить поведение по умолчанию для ->*, а именно вызвать указатель члена на объект, возвращаемый operator->, примерно так: operator ->*(memptr) { return ((*this)->).*memptr(); }. Но поскольку он не этого не делает, почему в типах указателей в C++11 этот оператор не определен? Есть ли причина?   -  person leemes    schedule 25.03.2013
comment
Ах, и я предполагаю, что это также применимо к C++03.   -  person leemes    schedule 25.03.2013
comment
А теперь расскажите мне, как вы передаете аргументы вызываемой функции-члену в вашем примере реализации. :) Это также должно сказать вам, почему в C++03 это еще хуже - нет идеальной переадресации.   -  person Xeo    schedule 25.03.2013
comment
Инкапсулируйте это и больше не думайте об этом: call(xsptr, f, args);   -  person Peter Wood    schedule 25.03.2013
comment
Это была только основная идея. Я никогда не реализовывал оператора вызова члена, поэтому не знаю. Может быть, вариативные аргументы шаблона?   -  person leemes    schedule 25.03.2013
comment
@PeterWood А что такое call? Есть ли std::call или вы имеете в виду, что я мог бы реализовать общую функцию call, которая справится с этим за меня?   -  person leemes    schedule 25.03.2013
comment
Да, реализуйте call самостоятельно.   -  person Peter Wood    schedule 25.03.2013


Ответы (2)


Предполагается ли, что компилятор отвергает стрелочный синтаксис сверху? Потому что обычно (при статическом вызове функции) вызывается оператор ->. Не следует ли ->* просто вызывать оператора ->?

Компилятор прав, отвергая синтаксис. Никакой operator->* не должен вызывать operator-> (согласно стандарту). Обратите внимание, что хотя синтаксис может выглядеть похожим, ->* является оператором сам по себе, а не композицией -> с какой-либо другой * вещью.

person David Rodríguez - dribeas    schedule 25.03.2013
comment
Да пока все хорошо. Я только что узнал об этом, спасибо! :) Но: Как последовательно решить этот вопрос? Предположим, у меня есть аргумент шаблона T, который может быть необработанным указателем или интеллектуальным указателем, t является его экземпляром, а f - указателем для вызова t. В настоящее время я пишу ((*t).*f)(), что уродливо, но, кажется, работает. - person leemes; 25.03.2013
comment
@leemes: auto& obj = *p; (obj.*f)(stuff...) - person Xeo; 25.03.2013
comment
@Xeo Что то же самое, не так ли? (Допустим, мне нужен разыменованный объект только для этого единственного выражения.) - person leemes; 25.03.2013
comment
@leemes: Если это только для этого единственного выражения, какая тебе разница? Кроме того, в этом примере std::bind(f, p, stuff...)() станет ((*t).*f)(stuff...). - person Xeo; 25.03.2013
comment
@leemes: Вы не можете. Вам нужно использовать средства, предоставляемые языком, нет возможности имитировать operator->, за которым следует operator.*. Хотя интеллектуальные указатели могли бы предложить аналогичную функциональность внутри с помощью шаблонов, это привело бы к раздуванию кода (новая функция для каждого типа члена в каждом типе класса, для которого вы использовали это) и предлагало небольшие преимущества (указатели на члены на самом деле не так уж и важны). часто встречается в реальном коде), и это не будет operator->*. Весь синтаксис и семантика указателя на член немного не в порядке, и большинству это даже не важно :) - person David Rodríguez - dribeas; 25.03.2013
comment
Спасибо вам обоим за то, что помогли мне понять это. :) - person leemes; 25.03.2013
comment
@leemes: я исправлен, вы можете реализовать его как operator->* для элементов данных. Я не уверен, что это можно сделать для членов, не являющихся данными (т.е. для членов функций), поскольку я не могу понять, каким должен быть тип, возвращаемый из перегруженного operator->* (для указателя U* и указателя на элемент T U::* возврат определяется как T&, но это не совсем подходит в случае функций-членов). - person David Rodríguez - dribeas; 25.03.2013

Вы можете перегрузить operator->* для интеллектуальных указателей. См. эту статью Скотта Мейерса. Основная идея состоит в том, чтобы вернуть объект функции, который будет вызываться после возврата из оператора.

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

person Artyom Chirkov    schedule 10.12.2015
comment
Конечно вы можете! Вам просто нужно определить его вне класса. Например, для unique_ptr код будет примерно таким: template <typename T, typename ReturnType> const PMFC<T, ReturnType, ReturnType (T::*)()> operator->*(const std::unique_ptr<T> &pObj, ReturnType (T::*pmf)()) { return std::make_pair(pObj.get(), pmf); } - person Artyom Chirkov; 14.12.2015