3

Consider the following two code snippets, The first one:

#include "pch.h"
#include <memory>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

class tcp_connection : public std::enable_shared_from_this<tcp_connection>
{
public:
    typedef std::shared_ptr<tcp_connection> pointer;

    static pointer create(boost::asio::io_service& io_service)
    {
        return pointer(new tcp_connection(io_service));
        //second example only differs by replacing the above line with the below one
        //return std::make_shared<tcp_connection>(io_service);
    }

private:
    tcp_connection(boost::asio::io_service& io_service) //private constructor
        : socket_(io_service)
    {
    }
    tcp::socket socket_;
};

int main()
{
    return 0;
}

The second one only differs from the first with one line, that is, the commented line.

With MSVC 2017 and boost::asio 1.68, the first version works as intended, while the second one doesn't compile, spitting out errors such as "incomplete type is not allowed tcp_async".

My question is:

  1. Is this because std::make_shared doesn't play along with std:std::enable_shared_from_this?
  2. Or, it is because assumptions that asio holds about how std::make_shared or std::enable_shared_from_this are implemented, doesn't hold with MSVC 2017.
  3. Or it's something else?
John Z. Li
  • 1,893
  • 2
  • 12
  • 19

1 Answers1

8

The problem in the code you show stems from the constructor of your type being private.

When you write new tcp_connection(io_service) the constructor is being referred to in the scope of tcp_connection itself, which has access.

However, std::make_shared (or whatever implementation detail it may employ) doesn't have access to the private constructor, so it can't initialize the object it's meant to have a shared pointer manage.

If the initialization is well-formed, std::make_shared works splendidly with std::enable_shared_from_this, but a private constructor makes it ill-formed.

A common workaround to this is to use the Passkey idiom. Which boils down to a public c'tor, but one that accepts a parameter of a private type. It would like somewhat like this1:

class tcp_connection2: public std::enable_shared_from_this<tcp_connection2>
{
    struct key{ explicit key(){} };
public:
    typedef std::shared_ptr<tcp_connection2> pointer;

    static pointer create(int io_service)
    {
        return std::make_shared<tcp_connection2>(io_service, key{});
    }

    tcp_connection2(int io_service, key) //private constructor, due to key
    {
    }
};

1 - I modified your class definition a bit, to make it easier for others to copy, paste and test this. But the same principle can be applied to your code.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458