2 votes

make_shared ne joue pas avec enable_shared_from_this ?

Considérons les deux extraits de code suivants, Le premier :

#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;
}

La seconde ne diffère de la première que par une ligne, à savoir la ligne commentée.

Avec MSVC 2017 et boost::asio 1.68, la première version fonctionne comme prévu, tandis que la seconde ne compile pas, et génère des erreurs telles que "incomplete type is not allowed tcp_async".

Ma question est la suivante :

  1. Est-ce parce que std::make_shared ne joue pas le jeu de std:std::enable_shared_from_this ?
  2. Ou bien, c'est parce que les hypothèses que asio retient sur la façon dont std::make_shared ou std::enable_shared_from_this sont implémentés, ne sont pas valables avec MSVC 2017.
  3. Ou c'est autre chose ?

6voto

StoryTeller Points 6139

Le problème dans le code que vous montrez vient du fait que le constructeur de votre type est privé.

Lorsque vous écrivez new tcp_connection(io_service) le constructeur est référencé dans la portée de tcp_connection lui-même, qui y a accès.

Cependant, std::make_shared (ou tout autre détail d'implémentation qu'il peut employer) n'a pas accès au constructeur privé, il ne peut donc pas initialiser l'objet qu'il est censé gérer avec un pointeur partagé.

Si l'initialisation est bien formée, std::make_shared fonctionne à merveille avec std::enable_shared_from_this mais un constructeur privé le rend illogique.

Une solution de contournement courante consiste à utiliser l'option idiome de la clé de passe . Ce qui revient à un c'tor public, mais qui accepte un paramètre de type privé. Cela ressemblerait un peu à ceci 1 :

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 - J'ai modifié un peu la définition de votre classe, pour qu'il soit plus facile pour d'autres de copier, coller et tester ceci. Mais le même principe peut être appliqué à votre code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X