Reputation: 4019
I'm relatively new to C++, having spent years with Obj-C, and wondering about how to add what would be closure block in Obj-C, to a C++ class. Here's some pseudo code of what I want to do:
class Slider
{
public:
void onMouseDown()
{
if(rightClick or ctlKeyDown)
{
if(myFunctionPointer != nil)
{
// show popup menu
myFunctionPointer(this);
}
}
}
FunctionPointer myFunctionPointer = nil;
};
class Editor
{
public:
void showPopupMenu(Slider *s)
{
// build the popupMenu with information based on the slider
}
void init()
{
// create a slider and connect the popupMenu function to it
mySlider = new Slider;
mySlider->functionPointer = showPopupMenu();
}
Slider *mySlider;
};
As you can see, I'm trying to get my Slider class to call a function without knowing anything about it.
This shouldn't be that difficult, but I'm interested in doing it the best/proper way. Lambdas and functors look incredibly confusing. Maybe I'm looking for something else. But what?
Upvotes: 2
Views: 4000
Reputation: 907
When it comes to treating function as objects, your basic options are: function pointer, functor/lambda, std::function. I am going to assume that you can find out their syntax and will focus on their difference.
A function pointer should be used when there is no need for a closure. This applies when the procedure you want to call is stateless or has a global state, and you have all parameters in the scope.
A functor should be used when you need to create a closure. Since functors are objects, you can maintain an internal state and pass parameters inside the closure.
A lambda is essentially a functor, without an explicit typename. The capture list of a lambda is its member if it were implemented as a functor instead. Note that you can overload operator()
for a functor but not a lambda.
The problem with functor/lambdas is that each of their definition has a different type, and can be ill-suited in function signature/class member types. std::function resolves the problem by being able to accept functor/lambda/function pointer and convert them to a uniform type of std::function. You pay a (often small) price for this flexibility in the form of performance though.
Upvotes: 7
Reputation: 118340
Lambdas and functors are one of the most advanced C++ topics. You're better off starting with some fundamentals before, and have a solid understanding of how C++ classes work.
But, since you asked, the C++ equivalent of this should be something like this:
class Slider
{
public:
void onMouseDown()
{
if(rightClick or ctlKeyDown)
{
if(myFunctionPointer)
{
// show popup menu
myFunctionPointer(this);
}
}
}
std::function<void (Slider *)> myFunctionPointer=nullptr;
};
class Editor
{
public:
void showPopupMenu(Slider *s)
{
// build the popupMenu with information based on the slider
}
void init()
{
// create a slider and connect the popupMenu function to it
mySlider = new Slider;
mySlider->functionPointer = [this](Slider *)
{
showPopupMenu();
};
}
Slider *mySlider;
};
As I said, I think that you're better off focusing your efforts on getting the fundamentals down pat, first, before plunging into these shark-infested waters.
And just to add some additional color: this will compile (the only thing that's missing is the definitions of rightClick
or ctlKeyDown
), but it may or may not be right, depending on the scope and the lifetime of the objects involved. It may or may not be necessary to have the lambda capture a std::shared_ptr
, instead of this
, depending on how the objects in this application get instantiated. Understanding how C++ objects work would be a necessary prerequisite before dealing with closures and callbacks, of this sort.
Upvotes: 3
Reputation: 4357
There are different ways to achieve what you're looking for. Here's a way, avoiding function pointers.
I didn't correct some other obvious mistakes, like the memory leak that arises from callingnew
and never deleting the object.
Best practice in this case would be using a std::unique_ptr
class Slider
{
public:
Slider(Editor& e)
: _e(e)
{ }
void onMouseDown()
{
if(rightClick or ctlKeyDown)
{
_e.showPopupMenu(this);
}
}
Editor& _e;
};
class Editor
{
public:
void showPopupMenu(Slider *s)
{
// build the popupMenu with information based on the slider
}
void init()
{
// create a slider and connect the popupMenu function to it
mySlider = new Slider(*this);
}
Slider* mySlider;
};
Here's another solution moving a functor directly in the constructor, however using templates.
template <typename Handler>
class Slider
{
public:
Slider(Handler&& h)
: _h(std::move(h))
{ }
void onMouseDown()
{
if(rightClick or ctlKeyDown)
{
// show popup menu
_h(this);
}
}
Handler _h;
};
class Editor
{
public:
void showPopupMenu(Slider *s)
{
// build the popupMenu with information based on the slider
}
void init()
{
// create a slider and connect the popupMenu function to it
mySlider = new Slider([this](Slider* s){ showPopupMenu(s); });
}
Slider *mySlider;
};
You could also use a std::function
instead, as shows on another answer
Upvotes: -1