10

Недавно я обнаружил, что shared_ptr не имеет указателя на оператор-член ->*. Я создал простой пример:О shared_ptr и указателе на оператор-член `-> *` и `std :: bind`

template <typename Pointer, typename Function, typename... Args> 
auto invoke1(Pointer p, Function f, Args... args) -> decltype((p->*f)(args...)) 
{ 
    return (p->*f)(args...); 
} 
struct A { 
    void g() { std::cout << "A::g()\n"; } 
}; 
int main() { 
    A a; 
    invoke1(&a, &A::g); // works!! 
    std::shared_ptr<A> sa = std::make_shared<A>(); 
    invoke1(sa, &A::g); // compile error!! 
} 

Q1: Почему это так? Почему у shared_ptr нет этого оператора?

Я добавил такой оператор для shared_ptr и пример начал работать:

template <typename T, typename Result> 
auto operator ->* (std::shared_ptr<T> pointer, Result (T::*function)()) ->decltype(std::bind(function, pointer)) 
{ 
    return std::bind(function, pointer); 
} 
template <typename T, typename Result, typename Arg1> 
auto operator ->* (std::shared_ptr<T> pointer, Result (T::*function)(Arg1 arg1)) ->decltype(std::bind(function, pointer, std::placeholders::_1)) 
{ 
    return std::bind(function, pointer, std::placeholders::_1); 
} 

Q2: Является ли это право реализации этого оператора? Существуют ли какие-либо «золотые» правила, как реализовать такой оператор, возможно, либо я заново изобрел колесо, либо пошел в совершенно неправильном направлении, как вы думаете? Есть ли способ, чтобы иметь одну функцию реализации этого оператора, а не как многие функции, как есть заполнители в станд ...

После этого я пришел к выводу, что std::bind можно использовать в моем invoke методе.

template <typename Pointer, typename Function, typename... Args> 
auto invoke2(Pointer p, Function f, Args... args) 
        -> decltype(std::bind(f, p, args...)()) 
{ 
    return std::bind(f, p, args...)(); 
} 

Таким образом, мой пример также работает без необходимости добавления operator ->* к shared_ptr.

Q3: То есть, std::bind теперь рассматривается как замена для operator->*?

+0

Вы пробовали: invoke1 (sa.get(), &A::g); – Alexis

+0

@Alexis - да, я пробовал и, конечно же, это работает, но я рассматривал это обходное решение как не важное для моего вопроса. – PiotrNycz

+0

Я даже не знал, что вы можете перегрузить этого оператора. – Mehrdad

ответ

6

В скорлупе ореха: yes std :: bind - это замена указателей на функцию-член.

Почему? потому, что функция члена указатели ужасны, и их только целями являются реализация делегатов, поэтому станд :: свяжи и станд :: функция сделать

Для справки о том, как реализуются функции члена указателей см моего предыдущего ответа here , Проще говоря, указатели функций-членов искалечены стандартом, потому что они не разрешают вызовы после приведения; это делает их совершенно бессмысленными для такого поведения, которое 90% людей хотят от указателей функций-членов: делегатов.

По этой причине std :: function используется для представления абстрактного типа «вызываемого», при котором std :: bind используется для привязки этого элемента к указателю функции-члена. Вы не должны вступать в беспорядки с указателями функций-членов и вместо этого использовать std :: bind и std :: function.

+1

Можете ли вы как-то объяснить «не разрешать звонки после кастинга», например? Я прочитал ваш ответ, но я пропустил объяснение. Во всяком случае, + 1x2 для обоих ваших ответов. – PiotrNycz

+1

@PiotrNycz. Одна из ключевых особенностей, необходимых для использования указателей функций-членов в качестве делегатов, - это возможность отбрасывать их между классами, например, от производного класса до базы. Хотя указатели функций каста являются законными, вызов того, который был ранее применен, не является. Это неопределенное поведение. Это означает, например, что вы не можете хранить список указателей на функцию-член и называть их полиморфно. Хотя это будет работать почти во всех компиляторах, оно нестандартно и, следовательно, не C++. – Alice

2

Я считаю, что shared_ptr не имеет оператора ->*, потому что его невозможно реализовать для произвольного количества аргументов (что позволяет C++ 11 для других случаев использования). Кроме того, вы можете легко добавить перегрузку функции invoke для интеллектуальных указателей, которая вызывает get(), поэтому усложнять интерфейс нежелательно.

+0

Невозможно для произвольного количества аргументов, но можно сказать до 30 аргументов (в моем компиляторе gcc есть 29 заполнителей). Так что практически говорящие функции с большим количеством аргументов отсутствуют в реальном мире. Во-вторых, я знаю, что есть обходные пути, @Alexis уже упоминал об этом. +1 для наблюдения, которое не может быть выполнено для любого количества аргументов. Но я все еще хочу знать, может быть, есть и другие причины, и по-прежнему нужны ответы на другие вопросы. – PiotrNycz

4

Я считаю, что самый простой soultion бы заменить 'структуру разыменования' (->) оператора с парой derefence (*) и ссылки структуры (.) операторы:

template <typename Pointer, typename Function, typename... Args> 
auto invoke1(Pointer p, Function f, Args... args) -> decltype(((*p).*f)(args...)) 
{ 
    return ((*p).*f)(args...); 
} 
+0

Вот как определяется _INVOKE_, в соответствии с которым работает 'std :: bind' –

+0

Приятная идея, спасибо. – PiotrNycz

+1

'. *' - это один оператор, а не два. Кроме того, '-> *' не совпадает с '->'. –