3

I'd like to create a modern (C++11 or C++14) implementation of the Active Object pattern that Herb Sutter describes here. A particular requirement of this implementation is that it support messages that involve safe transfer of ownership, via std::unique_ptr, of objects that would be prohibitively expensive to copy. For example, something close to the following should be supported:

void sink(std::unique_ptr<int>) { /* ... */ }

Active active;
std::unique_ptr<int> unique(new int(0));
active.Send(std::bind(sink, unique));

The following implementation, based closely on Herb's, does not work.

#include <thread>
#include <queue>

// (Implementation of a threadsafe `message_queue` omitted.)

class Active {
public:
  typedef std::function<void()> Message;

  Active() : thd([&]{ Run(); }) {
  }

  ~Active() {
    Send([&]{ done = true; });
    thd.join();
  }

  void Send(Message m) {
    mq.send(m);
  }

private:
  bool done = false;
  message_queue<Message> mq;
  std::thread thd;

  void Run() {
    while (!done) {
      Message msg = mq.receive();
      msg();
    }
  }
};

With this implementation, the example above fails to compile, with errors indicating an inability to convert from the bind type to std::function<void()>. This error stems from the fact that std::function requires its argument to be CopyConstructible, and binding a unique_ptr produces a non-copyable bind object.

Is there an alternative way to implement Active that avoids this issue?

Community
  • 1
  • 1
John
  • 29,546
  • 11
  • 78
  • 79
  • 1
    In C++14 you can use generalized lambda captures, so you can write `active.Send([u=std::move(unique)] { sink(std::move(u)); });`. – DanielKO Apr 15 '15 at 01:36
  • I haven't been able to get that to work. I believe the resulting lambda has the same problem as the bind expression -- it's non-copyable, and `std::function` requires CopyConstructible. – John Apr 15 '15 at 01:45
  • I understand your problem now. I think this is related: http://stackoverflow.com/questions/25421346/how-to-create-an-stdfunction-from-a-move-capturing-lambda-expression – DanielKO Apr 15 '15 at 02:01
  • Also this, which shows a workaround using `std::bind`: http://stackoverflow.com/questions/8236521/how-to-capture-a-unique-ptr-into-a-lambda-expression – DanielKO Apr 15 '15 at 02:05
  • 1
    `bind` or no `bind`, you can't pass `unique` like that without `move`. – Potatoswatter Apr 15 '15 at 05:30
  • I could have sworn that I have seen a `std::unique_function` paper proposal (with an N name and everything) for C++1z. – Yakk - Adam Nevraumont Apr 15 '15 at 13:54

1 Answers1

4

Here's a rough sketch of a type-erasing move-only wrapper capable of holding any move-constructible function object that can be called with no arguments.

struct Message {
    struct holder_base {
        virtual void operator()() = 0;
        virtual ~holder_base() = default;
    };

    template<class F>
    struct holder : holder_base {
        holder(F&& f) : func(std::move(f)) {}
        void operator()() override { func(); }
        F func;
    }; 

    Message() = default;
    Message(Message&&) = default;
    ~Message() = default;
    Message& operator=(Message&&) = default;

    // copy members implicitly deleted

    template<class F>
    Message(F func) : p_func(new holder<F>(std::move(func))) {}

    void operator()() const { (*p_func)(); }
    std::unique_ptr<holder_base> p_func;
};
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • It takes 3 additional lines, and modifications to 4 other lines, to make this arbitrary signature instead of fixed signature. Mind if I upgrade it? Then all you need is SFINAE test on that ctor to make it usable. Hurm, discarding results for `void` is actually annoying, nm. – Yakk - Adam Nevraumont Apr 15 '15 at 13:46