admin管理员组

文章数量:1303335

I'm relatively new to C++, please five 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?

I'm relatively new to C++, please five 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?

Share Improve this question edited Feb 4 at 15:51 Stefan asked Feb 4 at 15:24 StefanStefan 1,3094 gold badges17 silver badges29 bronze badges 5
  • why is private ? how can you access to that ? – R.F. Commented Feb 4 at 15:40
  • 1 Side question: Why is TaskCallback taking a void* instead of a MyClass& (or MyClass*)? – Ted Lyngmo Commented Feb 4 at 15:50
  • 1 void (*callback)(void *userData) is the C-Way, C++ would simply be std::function<void()> (context might be captured if needed). – Jarod42 Commented Feb 4 at 15:51
  • @Ted Lyngmo Thank you, good point! It helps to think about it. For generic purpuses it is needed in the typedef std::function<void(void *)> Task. But for the TaskCallback in MyClass I can indeed make it MyClass* or even omit it completely (based on Caleth his answer). – Stefan Commented Feb 4 at 16:09
  • 1 @Stefan Yes, you can remove it here but if you do have a callback that takes a void* you'll need to do all sorts of testing to check what type it is that you actually got in the call - and you'll also need to check if you got a nullptr before dereferencing. One alternative would be to take a base class reference and use runtime polymorphism. – Ted Lyngmo Commented Feb 4 at 16:45
Add a comment  | 

2 Answers 2

Reset to default 5

void (*callback)(void *userData) is the C-Way,
C++ would simply use std::function<void()> ("context" might be captured if needed).

So, it becomes:

#include <functional>

// Define the task signature.
using Task = std::function<void()>;

class ImportantWork
{
public:
    // A function taking a long time,
    // which will be calling the task when the time has come.
    void PerfomWorkTakingLongTime(Task task)
    {
        // Do a lot of work...

        // Now it is time to call the task.
        task();

        // Perhaps do some more work.
    }
};

class MyClass
{
public:
    void StartWork()
    {
       // Start work taking a long time and provide the TaskCallback as task.
       importantWork.PerfomWorkTakingLongTime([this]() { TaskCallback(); });
    }

private:
    void TaskCallback() { TaskVariable += 1; }
    int TaskVariable = 0;
    ImportantWork importantWork;
};

Demo

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.

本文标签: cAssigning a class member function to a variable of type stdfunctionltvoid(void *)gtStack Overflow