I'm relatively new to C++, please forgive some imperfections in my formulation.
Summary:
Basically I have a class member function and a variable of type std::function<void(void *)>
. I want to assign my class member function to this variable.
Using a lambda expression I get this to work:
typedef std::function<void(void *)> Task;
Task task = [&](void *context) { ClassMemberFunction(context); };
But this lambda is capturing the class itself as reference (because of [&]
) and this seems unnecessary as that is what the context
argument is for.
Is there a better/cleaner way to achieve this?
TLDR, complete picture:
I have an object that has to perform a task
at some point. You can consider this a callback
pattern for example.
My task
is defined as a function. The task
must have some context to operate on/with.
My task
does not return a value, but it may store results in the context.
I try to make this generic so it does not matter what kind of task this is and what kind of context it requires.
I made some code (which I tested to compile and run):
/// ImportantWork.h
#include <functional>
// Define the task signature.
typedef std::function<void(void *)> Task;
class ImportantWork {
public:
// A function taking a long time, which will be calling the task when the time has come.
void PerfomWorkTakingLongTime(Task task, void *taskContex);
};
/// ImportantWork.cpp
#include "ImportantWork.h"
void ImportantWork::PerfomWorkTakingLongTime(Task task, void *taskContex) {
// Do a lot of work.
// Now it is time to call the task.
task(taskContex);
// Perhaps do some more work.
}
Now it happens that my task
is a class member function.
In general it is not necessarily that the task
will always be a class member function, it could also be a function not in a class. But my 'problem' occurs when it is a class member function.
/// MyClass.h
#include "ImportantWork.h"
class MyClass {
public:
void StartWork();
private:
void TaskCallback(void *context);
int TaskVariable = 0;
ImportantWork importantWork;
};
/// MyClass.cpp
#include "MyClass.h"
void MyClass::StartWork() {
// Start work taking a long time and provide the TaksCallback as task and a reference to the MyClass instance as context.
importantWork.PerfomWorkTakingLongTime(
[&](void *context) { TaskCallback(context); }, // -- My question is about this line. -- //
this);
}
void MyClass::TaskCallback(void *context) {
// Manipulate a MyClass instance member.
TaskVariable += 1; // -- My question is about this line also. -- //
// This does also work. Somehow, I expected this to be the only way.
MyClass *pContext = (MyClass *)context;
pContext->TaskVariable += 1;
}
/// Main.cpp
#include "MyClass.h"
int main(void) {
MyClass myInstance;
myInstance.StartWork();
return 0;
}
This seems to work, however, I'm puzzled by my lambda expression (see MyClass.cpp): [&](void *context) { TaskCallback(context); }
.
[&]
is capturing my class (this
) implicitly as context. However, one line below, I provide the context explicitly as this
.
I understand that a class member function needs a reference to its class instance. It seems that the compiler is implicitly providing this to the function as a sort of 'under the hood' function argument.
In case of the lambda expression, [&]
seems to takes care of this.
However, it feels like I'm doubling things, providing the context twice.
Somehow I feel that I need a lambda expression where I don't want to capture any context by reference [&]
or copy [=]
, just capturing nothing but it seems not to be possible.
Is there a good way to achieve what I'm trying.
Thank you.
EDIT:
I can make my class member function static
that way it has to use the explicit context provided. Still, the lambda expression is capturing context because of the [&]
. Can this be avoided? Can I avoid using a lambda expression at all?
Somehow I feel that I need a lambda expression where I don't want to capture any context by reference [&] or copy [=], just capturing nothing but it seems not to be possible. Is there a good way to achieve what I'm trying.
You can capture nothing, but then there's no this
on which to call TaskCallback
, so you have to get it from context
.
importantWork.PerfomWorkTakingLongTime(
[](void *context) { static_cast<MyClass*>(context)->TaskCallback(nullptr); },
this);
Alternatively, you can capture this
, so you can ignore context
.
importantWork.PerfomWorkTakingLongTime(
[this](void *) { TaskCallback(nullptr); },
nullptr);
What you are doing is both, so you have redundancy in accessing the instance of your class.
Still, the lambda expression is capturing context because of the [&].
No it isn't. context
is the parameter of the lambda, it only captures things accessible from the scope it is defined in, that it uses.
N.b. in both of these I'm passing nullptr to TaskCallback
, as that only needs the members of MyClass
. It would be better to remove that parameter.