2015-06-16 4 views
0

У меня есть класс, который должен вызывать функцию, указанную пользователем в определенных случаях. Поэтому класс имеет метод void setExternalPostPaintFunction(void(*function)(QPainter&));, который может использоваться для «регистрации» функции. Затем эта функция будет вызываться по этому поводу:C++: Сохранить указатель на функцию-член объекта в другом объекте

class A { 
    public: 
     void setExternalPostPaintFunction(void(*function)(QPainter&)); 
    private: 
     void (*_externalPostPaint)(QPainter&); 
     bool _externalPostPaintFunctionAssigned; 
}; 

Указатель функции сохраняется в переменной-члене _externalPostPaint. Реализация setExternalPostPaintFunction выглядит следующим образом:

void A::setExternalPostPaintFunction(void(*function)(QPainter&)) { 
    _externalPostPaint = function; 
    _externalPostPaintFunctionAssigned = true; 
} 

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

class A { 
    public: 
     template <typename T> 
     void setExternalPostPaintFunction(void(T::*function)(QPainter&), T* object); 
    private: 
     void (T::*_externalPostPaint)(QPainter&);  //<- This can't work! 
     bool _externalPostPaintFunctionAssigned; 
}; 

Таким образом, я могу передать указатель на функцию и указатель на объект setExternalPostPaintFunction и, вероятно, будет в состоянии вызвать функцию на объект внутри этой функции. Но я не могу сохранить его в переменной _externalPostPaint, потому что тип T выведен только при вызове функции setExternalPostPaintFunction, поэтому я не могу иметь переменную-член, которая зависит от этого типа, поскольку тип моей переменной-члена имеет чтобы быть известным, когда объект создан, и, кроме того, он не может измениться, но он будет иметь место в случае, когда назначается новая функция, которая, возможно, может быть функцией-членом объекта другого типа.

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

Параметр Anoter, безусловно, должен был бы создать класс функтора с функцией виртуального члена, который может быть перезаписан в производном классе, а затем передать + сохранить указатель объекта этого типа вместо указателя функции. Но я как-то предпочел бы мой подход, если это возможно.

EDIT: РЕШЕНИЕ

TartanLlama привел меня на правильном пути, предложив использовать std::function. Вот как я ее решил:

class A { 
    public: 
     template <typename T> 
     void setExternalPostPaintFunction(T* object, void(T::*function)(QPainter&)) { 
      _externalPostPaint = std::bind(function, object, std::placeholders::_1); 
      _externalPostPaintFunctionAssigned = true; 
     } 
     void setExternalPostPaintFunction(std::function<void(QPainter&)> const& function); 
    private: 
     std::function<void(QPainter&)> _externalPostPaint; 
     bool _externalPostPaintFunctionAssigned; 
}; 

Как вы видите, указатель на функцию функции/члена хранится в std::function<void(QPainter&)> объекта в настоящее время. Преимущество состоит в том, что std::function может в основном хранить любую вызываемую цель. Затем есть две перегрузки: одна, которая может использоваться для любого объекта std::function, который также принимает, например. нормальный указатель функции (потому что std::function, который, как ожидается, затем неявно построен из этого), и один для функций-членов, которые должны быть вызваны на объект (более для удобства). Последний реализован как шаблон. Это использует std::bind для создания объекта std::function вызова этой функции-члена (пользователь передал) на объект (пользователь прошел).

Перегрузка, которая принимает std::function реализуется в исходном файле так:

void ImageView::setExternalPostPaintFunction(std::function<void(QPainter&)> const& function) { 
    _externalPostPaint = function; 
    _externalPostPaintFunctionAssigned = true; 
} 

Вызов этой хранимой функции в коде класса A теперь так же просто, как:

//canvas is a QPainter instance  
if (_externalPostPaintFunctionAssigned) _externalPostPaint(canvas); 

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

//_imageView is an instance of "A" 
//"MainInterface" is the type of "this" 
_imageView->setExternalPostPaintFunction(this, &MainInterface::infoPaintFunction); 

Или, если это не функция-член, а просто нормальная функция:

void someFunction(QPainter& painter) { 
    //do stuff 
} 

_imageView->setExternalPostPaintFunction(&someFunction); 

Или он может явно создать std::function объект и передать его:

std::function<void(QPainter&)> function = [&](QPainter& painter){ this->infoPaintFunction(painter); }; 
_imageView->setExternalPostPaintFunction(function); 

работает как шарм.

+1

Является ли C++ 11 хорошо? 'std :: function' идеально подходит для такого рода вещей. – TartanLlama

+0

Да, C++ 11 в порядке. Я посмотрю на это. – user1488118

ответ

2

Вы можете использовать std::function:

class A { 
    public: 
     //PostPaintFun can be anything which acts as a function taking a QPainter& 
     //Could be a lambda, function pointer, functor, etc. 
     using PostPaintFun = std::function<void(QPainter&)>; 
     void setExternalPostPaintFunction(PostPaintFun fun); 
    private: 
     //Names beginning with an underscore are reserved, don't use them 
     //Ending with an underscore is fine 
     PostPaintFun fun_; 
     bool externalPostPaintFunctionAssigned_; 
}; 

Теперь вы можете использовать функции-члены, как так:

struct B 
{ 
    void exec(QPainter&) const; 
}; 

void foo() { 
    B b; 
    a.setExternalPostPaintFunction(
     [b] (QPainter& p) {b.exec(p);} 
    ); 
} 

//or inside B 
void B::foo() { 
    a.setExternalPostPaintFunction(
     [this] (QPainter&p) {this->exec(p);} 
    ); 
} 
+0

Спасибо за подсказку'std :: function '. Ваше решение с использованием лямбда очень элегантно. Однако я решил немного по-другому, потому что я думаю, что это несколько проще для пользователя этого класса. Моему способу он не должен сам создавать эту лямбду, но может просто передать указатель на свой объект и указатель на функцию-член, а моя функция 'setExternalPostPaintFunction' делает остальные, используя' std :: bind'. Я редактировал свой вопрос и добавил свое решение. Может быть, вы могли бы взглянуть на него и рассказать мне, что вы думаете об этом. – user1488118

+0

Прохладный, это довольно приличное решение, но не позволяет пользователям проходить в лямбдах или функторах. Я хотел бы предложить один конструктор, который принимает 'std :: function', затем тот, который для удобства использует указатель на функцию-член и указатель экземпляра. – TartanLlama

+0

Хорошо, уже думал об этом. Думаю, я добавлю еще одну перегрузку, которая непосредственно принимает 'std :: function'. – user1488118

0

Я должен сказать, что я предпочитаю ответ TartanLlama, но здесь у вас есть что-то, что может работать на вы.

Возможно, это потребует некоторой работы, но я уверен, что вы получите эту идею.

struct IFunctionHolder {};      // Used for pointing to any FunctionHolder 
typedef IFunctionHolder* functionHolder_ptr; // Alias for IFunctionHolder* . 

template<typename Function>      // The template for the actual function holders. 
struct FunctionHolder: public IFunctionHolder 
{ 
    Function function; 
}; 


class A { 
    public: 
     template <typename T> 
     void setExternalPostPaintFunction(void(T::*function)(QPainter&), T* object); 
    private: 
     functionHolder_ptr *function_holder;   // This memeber can hold eny instantiation of template<> FunctionHolder. 
                 // Instantiate this member wen calling setExternalPostPaintFunction 
     bool _externalPostPaintFunctionAssigned; 
}; 

Вы могли бы иметь некоторый код, как это:

A some_a; 
void some_a.setExternalPostPaintFunction(&SomeInstance::some_fnunction); // Here take place the instantiation of FunctionHolder. 
some_a.function_holder.function(some_painter); 
+0

Я решил пойти с подходом 'std :: function'. Я добавил свое решение к вопросу. – user1488118

Смежные вопросы