35 votes

Vérification de l'existence d'une fonction membre C++, éventuellement protégée

Je tente de détecter si une classe possède une fonction particulière (spécifiquement shared_from_this(), qui est héritée de std::enable_shared_from_this). Pour compliquer les choses, je dois savoir si cette fonction existe même si elle a été héritée d'une classe de base lointaine ou héritée en utilisant un accès protégé.

J'ai regardé d'autres questions telles que celle-ci, mais les méthodes proposées ne fonctionnent pas pour détecter les fonctions membres protégées.

La méthode actuelle que j'utilise est la suivante :

template 
struct shared_from_this_wrapper : public T
{
  template 
  static auto check( U const & t ) -> decltype( t.shared_from_this(), std::true_type() );

  static auto check( ... ) -> decltype( std::false_type() );
};

template
struct has_shared_from_this : decltype(shared_from_this_wrapper::check(std::declval>()))
{ };

Le défaut de ma solution actuelle est qu'elle ne fonctionne pas avec des classes déclarées final. Je suis donc à la recherche d'une solution pour tester une fonction membre qui satisfait :

  1. Fonctionne avec des classes déclarées final
  2. Fonctionne avec des fonctions membres protégées
  3. Fonctionne avec l'héritage
  4. N'a pas besoin de connaître le type de retour de la fonction
  5. Compile sous gcc, clang et MSVC 2013 (ce dernier limitant éventuellement une SFINAE trop sophistiquée)

Édit : J'ai une solution potentielle qui fonctionne mais qui nécessite de rendre ami une classe auxiliaire, ce qui n'est pas non plus une solution idéale mais peut être une solution de contournement pour le moment (car elle satisfait toutes les exigences) :

struct access
{
  template 
  static auto shared_from_this( T const & t ) -> decltype( t.shared_from_this() );
};

template 
static auto check( U const & t ) -> decltype( access::shared_from_this(t), std::true_type() );

static auto check( ... ) -> decltype( std::false_type() );

template
struct has_shared_from_this2 : decltype(check(std::declval()))
{ };

struct A : std::enable_shared_from_this {};
struct B : protected A { friend class access; };    

Un autre édit : exemples de classes et ce qu'un type doit renvoyer en vérifiant l'existence de quelque chose comme shared_from_this :

struct A : std::enable_shared_from_this {}; // devrait renvoyer vrai struct B final : protected A {}; // devrait renvoyer vrai struct C : A {}; // devrait renvoyer vrai struct D {}; // devrait renvoyer faux

Je devrais mentionner que mon objectif final en détectant si cette fonction existe est de déterminer le type de retour de celle-ci afin de comprendre le type sur lequel std::enable_shared_from_this a été instancié. Hériter de std::enable_shared_from_this vous donne std::shared_ptr shared_from_this(), et T est finalement ce que j'ai besoin de comprendre. Cela est nécessaire pour une sérialisation correcte des types qui héritent de std::enable_shared_from_this.

Édit partie 3 : La modification :

Cela est fait pour la bibliothèque de sérialisation cereal et en tant que tel, je n'ai aucun contrôle sur la manière dont un utilisateur souhaite concevoir sa classe. Je voudrais pouvoir sérialiser n'importe quel type d'utilisateur qui dérive de std::enable_shared_from_this, ce qui inclut les utilisateurs qui déclarent leurs classes comme final ou utilisent l'héritage protégé quelque part dans le processus. Toute solution qui nécessite de manipuler le type réel qui est vérifié n'est pas une solution valide.

1voto

Alex Points 3973

J'ai réfléchi à la façon de mettre en œuvre les choses que vous avez demandées et je suis arrivé à une conclusion totalement différente.

Le problème en question est très intéressant : Comment vérifier si une classe implémente une interface masquée. Malheureusement, le problème contredit le principe de substitution de Liskov ; l'un des principes fondamentaux de la programmation orientée objet.

Cela est en partie dû à la structure de type de std::shared_ptr. shared_ptr ne reflète pas la relation d'héritage de ses types d'argument. Étant donné une classe T et une classe U, où class T : public U {}; alors shared_ptr : public shared_ptr {}; ne le fait pas !

Votre implémentation comporte une faille fondamentale au niveau de l'interface. Si vous vérifiez au moment de la compilation si une fonction existe et que vous extrayez ensuite le type, vous ne pourrez désérialiser que des structures de données utilisant des pointeurs partagés.

De plus, si std::shared_ptr est obsolète ou si vous souhaitez utiliser un autre moyen pour acquérir de la mémoire (std::allocator par exemple ? une allocation de région/pool), vous devrez adapter vos interfaces.

Mon avis personnel est de créer une sorte d'interface de fabrique et de l'enregistrer quelque part dans le désérialiseur.

La deuxième serait d'avoir une classe de fabrique qui expose une interface de modèle implicite (et utilise CRTP pour spécialiser l'interface en fonction des besoins de l'utilisateur. Par exemple :

template >
class Deserializable {
  static IfType alloc(ActualType &&t) {
    Allocator a; // choisissez une autre interface selon vos besoins.
    return a.allocate(t); /* à implémenter */
  }
private:
};

class MyClass
 : public InterfaceClass,
   public Deserializable {
  /* votre contenu ici */
};
  • Cela vous offre un niveau d'abstraction raisonnable dans vos classes de modèle.
  • L'utilisateur de votre bibliothèque sait ce qu'il veut en retour de toute façon. Et s'il choisit d'allouer autre chose qu'un std::shared_ptr, il pourrait le faire (en créant sa propre Allocator).
  • L'utilisateur n'a pas besoin d'implémenter quoi que ce soit, il doit simplement spécifier les types (et les passer réellement à votre code, pas de suppositions).

On pourrait interpréter cela comme une classe de politique (pas dans le sens strict d'Andrei Alexandrescu). La bibliothèque de sérialisation impose une politique d'allocation. L'utilisateur peut décider de la mise en œuvre de cette politique. Dans ce cas, un choix sur comment allouer l'objet désérialisé et le type, qui pourraient être différents. En raison de l'implémentation par défaut de l'Allocator et de son argument de modèle, un autre choix est proposé à l'utilisateur si nécessaire.

Pour comprendre la puissance de cette approche, je vous invite à examiner le code de boost::operator qui utilise cette technique pour spécifier le type de retour et les arguments des opérateurs arithmétiques au moment de la compilation.

Remarque

Pour les personnes cherchant également des réponses au problème d'origine, je vous suggère d'utiliser cette approche. Cependant, cela nécessite que le membre soit public, car il vérifie un pointeur de fonction membre portant un nom donné.

0voto

edwinc Points 618

Je vais me permettre de remettre en question la question. Tous les objets passés à travers un shared_ptr n'héritent pas de enable_shared_from_this.

Peut-être que c'est ce que vous cherchez ou cela pourrait vous donner quelques idées supplémentaires:

class Foo1 { };
class Foo2 : public std::enable_shared_from_this< Foo2 > {};
class Foo3  final : protected Foo2 {};

struct Serialize
{
    template 
    void write( T* ) {  printf( "non partagé!\n" ); }

    template 
    void write( std::shared_ptr ) { printf( "partagé!\n" ); }
};

int test( )
{
    typedef std::shared_ptr Foo2Ptr;
    typedef std::shared_ptr Foo3Ptr;

    Serialize   s;
    Foo1*       pFoo1 = nullptr;
    Foo2Ptr     pFoo2;
    Foo3Ptr     pFoo3;
    s.write( pFoo1 );
    s.write( pFoo2 );
    s.write( pFoo3 );

    return 0;
}

Au moment de l'exécution, la sortie est:

non partagé!
partagé!
partagé!

-1voto

ibizaman Points 428

Si le seul objectif est de détecter le type T, alors je vous suggère de faire comme dans la STL et d'ajouter un typedef :

template 
struct enable_shared_from_this : public T
{
    typedef T base_type;
    // ...
};

Ensuite, vous pouvez l'utiliser comme ceci :

class A : enable_shared_from_this
{
}

A::base_type // == B

**

Cet exemple suppose que vous savez que A hérite du shared_from_this_wrapper.

**

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