Marc
Marc

Reputation: 16512

pass object by value between threads

I'm trying to pass objects by value between threads with a queue.

Because there are multiple different objects, I made an abstract class.

class message
{
public :
      virtual ~message(){};
}; 

then I have a subclass for each different type of message

class a_specific_message : public message
{
...
};

I read this tutorial for implementing the queue and I call it the following way:

concurrent_queue<message> queue;
a_specific_message m{1, 2, 3};
queue.push(m);

My problem is that I need to override the operator= so the queue can clone the message

popped_value=the_queue.front();

I tried to add a virtual operator but it doesn't get called in the subclass.

I don't know how I could achieve something like this without passing object by reference.

Upvotes: 0

Views: 249

Answers (2)

Guillaume Racicot
Guillaume Racicot

Reputation: 41750

In my opinion, polymorphism is not what you really want. An interface with nothing but a destructor and dynamic downcast usually means that you are using the wrong tool.


However, if polymorphism is what you want, let me propose std::unique_ptr. You can have here you queue of pointers declared like that: concurrent_queue<std::unique_ptr<message>>

As you told in the comments:

I didn't want to use pointers because devs will have to remember to delete it in another thread. It could be easy to forget and have a memory leak.

Then I think std::unqiue_ptr is the right thing for you.

If I translate your code, it would look like that:

concurrent_queue<std::unique_ptr<message>> queue;
auto m = std::make_unique<a_specific_message>(1, 2, 3);

queue.push(std::move(m));

Or simply:

concurrent_queue<std::unique_ptr<message>> queue;

queue.push(std::make_unique<a_specific_message>(1, 2, 3));
// Or
queue.push(new a_specific_message{1, 2, 3});

Then, to pop values:

auto popped_value = std::move(the_queue.front());

Then everything is deleted automatically, without having to remember to delete any pointer. std::unique_ptr has been created for that purpose.

To avoid the explicit move in your code, you could have something like pop_value in your queue, implemented like that:

T pop_value() {
    auto value = std::move(the_queue.front());

    the_queue.pop();

    // use nrvo
    return value;
}

So now, in your threads, you can safely do that:

{
    auto some_message = queue.pop_value();

    // stuff

} // some_message deleted here.

Upvotes: 2

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136208

As @PeteBecker writes in the comments, concurrent_queue<message> holds message's by value. When a derived object instance is pushed into the queue it makes a copy of message part only. Slicing occurs.

One way to make the queue hold objects of multiple types without resorting to pointers is to use a discriminated union, e.g. boost::variant:

using message = boost::variant<specific_message_a, specific_message_b>;

No common base class for messages is required here.

The downside of this approach is that sizeof(message) is sizeof of the largest type in that boost::variant<> template argument list.

Upvotes: 2

Related Questions