I have to make a thread-safe work queue which can have work popped on in different threads and it will process it on a worker thread. The work can be very generic so I was thinking using lambdas with capture as a good way to allow this. I have the following code as a starter:
#include <iostream>
#include <vector>
#include <functional>
typedef std::function<void()> Task;
typedef std::vector<Task> TaskQueue;
class Queue
{
public:
void flush() {
for (auto it : m_queue) {
it();
}
}
// will add thread safe locks later.
void queue(Task task) {
m_queue.push_back(task);
}
private:
TaskQueue m_queue;
};
Queue q;
class WorkMaker
{
public:
WorkMaker(int inA) : m_a(inA) {}
void MakeWork() {
q.queue([&]{
std::cout << this->m_a << std::endl;
});
}
private:
int m_a;
};
int main()
{
WorkMaker w1(1);
WorkMaker w2(2);
w1.MakeWork();
w2.MakeWork();
q.flush();
return 0;
}
Is there something inherently unperformant about this code or will the compiler optimize it out? Also is passing a lambda into a std::function
argument by value copying the lambda or just the pointer to it?
EDIT:
I think i can solve the problem of memory ownership by using shared_ptr's and passing them into the lambda instead. Consider the following modification:
typedef std::function<void()> Task;
typedef std::deque<Task> TaskQueue;
class Queue
{
public:
void flush() {
while (!m_queue.empty()) {
auto it = m_queue.front();
m_queue.pop_front();
it();
}
}
// will add thread safe locks later.
void queue(Task task) {
m_queue.push_back(task);
}
private:
TaskQueue m_queue;
};
Queue q;
class WorkMaker : public std::enable_shared_from_this<WorkMaker>
{
public:
WorkMaker(int inA) : m_a(inA) {}
~WorkMaker() { std::cout << "Destroy " << m_a << std::endl; }
void MakeWork() {
std::shared_ptr<WorkMaker> self = shared_from_this();
q.queue([self]{
std::cout << self->m_a << std::endl;
});
}
int m_a;
};
int main()
{
{
auto w1 = std::make_shared<WorkMaker>(1);
auto w2 = std::make_shared<WorkMaker>(2);
w1->MakeWork();
w2->MakeWork();
}
q.flush();
return 0;
}
I get the desired output as :
1
Destroy 1
2
Destory 2
Creating a Lambda Expression in C++auto greet = []() { // lambda function body }; Here, [] is called the lambda introducer which denotes the start of the lambda expression. () is called the parameter list which is similar to the () operator of a normal function.
We have seen that lambda is just a convenient way to write a functor, therefore we should always think about it as a functor when coding in C++. We should use lambdas where we can improve the readability of and simplify our code such as when writing callback functions.
They can capture their environment by value, by reference, and with C++14 by move. If the functionality of your callable is short and self-explanatory, use a lambda function. A lambda function is, in general, faster and easier to understand.
Lambdas Make Code More Readable The first point might sound quite obvious, but it's always good to appreciate the fact that since C++11, we can write more compact code. For example, recently, I stumbled upon some cases of C++03/C++0x with bind expressions and predefined helper functors from the Standard Library.
The std::function
will make a private copy of the function pointer, lambda or whatever it refers to. Typically, this copy is referenced from the std::function
object so that further copying is later avoided.
There is nothing particularly slow with using std::function
objects this way. However, you should probably think about replacing the std::vector
by a std::deque
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With