Search code examples
c++c++11visual-c++c++-actor-framework

Can I use C++11 smart pointers as a message return type in the C++ Actor Framework?


When I use a unique_ptr as the return type, I receive compiler error C2280:

'caf::detail::tuple_vals<std::unique_ptr<A,std::default_delete<_Ty>>>::tuple_vals(const caf::detail::tuple_vals<std::unique_ptr<_Ty,std::default_delete<_Ty>>> &)': attempting to reference a deleted function  include\caf\detail\tuple_vals.hpp   102

Here's some example code that illustrates the issue (modified from one of the C++ Actor Framework examples):

#include <iostream>
#include "caf/all.hpp"

using namespace caf;
using namespace std;

class A
{
public:
    int a;

    A(int a)
    {
        this->a = a;
    }
};

using a_type = typed_actor<replies_to<int>::with<unique_ptr<A>>>;

a_type::behavior_type a_behavior(a_type::pointer self)
{
    return
    {
        [self](const int& a) -> unique_ptr<A>
        {
            return make_unique<A>(5);
        }
    };
}

void tester(event_based_actor* self, const a_type& testee)
{
    self->link_to(testee);
    // will be invoked if we receive an unexpected response message
    self->on_sync_failure(
        [=]
        {
            aout(self) << "AUT (actor under test) failed" << endl;
            self->quit(exit_reason::user_shutdown);
        });
    self->sync_send(testee, 5).then(
        [=](unique_ptr<A> a)
        {
            if(a->a == 5)
            {
                aout(self) << "AUT success" << endl;
            }

            self->send_exit(testee, exit_reason::user_shutdown);
        }
    );
}

Solution

  • CAF requires that each type in a message is regular. This implies that it must provide a copy constructor, which std::unique_ptr doesn't have. Therefore the compiler complains.

    Messages have a copy-on-write implementation. You can cheaply copy a single message, as it just bumps the reference count internally. At any time, you can perform const-access of the message elements. The "write" part of copy-on-write only kicks in if the reference count of a message is greater than 1 and you request non-const access. At that point the runtime invokes the copy constructor of the contained type to create a new message.

    If CAF would unconditionally perform this copy, independent of the reference count, then it would not be possible to efficiently support data flow programming, where an actor receives a message, modifies its contents, and forwards on to the next stage.

    Think of messages as pointer containers: the contained elements are allocated on the free store. Storing a pointer in a message is usually a design flaw. The double-wrapping also stresses the allocator unnecessarily.

    Since you can slice messages flexibly, you get away with creating a single message and can then use the contained value in various contexts.