web-dev-qa-db-fra.com

clang: aucune définition de méthode virtuelle hors ligne (pure classe C ++ abstraite)

J'essaie de compiler le code C++ simple suivant en utilisant Clang-3.5:

test.h:

class A
{
  public:
    A();
    virtual ~A() = 0;
};

test.cc:

#include "test.h"

A::A() {;}
A::~A() {;}

La commande que j'utilise pour compiler ceci (Linux, uname -r: 3.16.0-4-AMD64):

$clang-3.5 -Weverything -std=c++11 -c test.cc

Et l'erreur que j'obtiens:

./test.h:1:7: warning: 'A' has no out-of-line virtual method definitions; its vtable will be emitted in every translation unit [-Wweak-vtables]

Des indices pourquoi cela émet un avertissement? Le destructeur virtuel n'est pas du tout en ligne. Bien au contraire, il existe une définition hors ligne fournie dans test.cc. Qu'est-ce que j'oublie ici?

Modifier

Je ne pense pas que cette question soit un double de: Quelle est la signification des -wweak-vtables de clang? comme l'a suggéré Filip Roséen. Dans ma question, je me réfère spécifiquement aux classes abstraites pures (non mentionnées dans le doublon suggéré). Je sais comment -Wweak-vtables fonctionne avec des classes non abstraites et je suis d'accord avec ça. Dans mon exemple, je définis le destructeur (qui est purement abstrait) dans le fichier d'implémentation. Cela devrait empêcher Clang d'émettre des erreurs, même avec -Wweak-vtables.

33
banach-space

Nous ne voulons pas placer la table virtuelle dans chaque unité de traduction. Il doit donc y avoir un certain ordre d'unités de traduction, de sorte que nous pouvons dire alors, que nous plaçons la table dans la "première" unité de traduction. Si cette commande n'est pas définie, nous émettons l'avertissement.

Vous trouverez la réponse dans le Itanium CXX ABI . Dans la section sur les tables virtuelles (5.2.3), vous trouverez:

La table virtuelle pour une classe est émise dans le même objet contenant la définition de sa fonction clé, c'est-à-dire la première fonction virtuelle non pure qui n'est pas en ligne au point de définition de la classe. S'il n'y a pas de fonction clé, elle est émise partout utilisée. La table virtuelle émise comprend le groupe de tables virtuelles complet pour la classe, toutes les nouvelles tables virtuelles de construction requises pour les sous-objets et le VTT pour la classe. Ils sont émis dans un groupe COMDAT, avec le nom de table virtuelle mutilé comme symbole d'identification. Notez que si la fonction clé n'est pas déclarée en ligne dans la définition de classe, mais que sa définition est ultérieurement toujours déclarée en ligne, elle sera émise dans chaque objet contenant la définition.
[~ # ~] note [~ # ~]: Dans l'abstrait, un destructeur virtuel pur pourrait être utilisé comme fonction clé, car il doit être défini même s'il est est pur. Cependant, le comité ABI n'a réalisé ce fait qu'après la spécification de la fonction clé; donc un destructeur virtuel pur ne peut pas être la fonction clé .

La deuxième section est la réponse à votre question. Un destructeur virtuel pur n'est pas une fonction clé. Par conséquent, il n'est pas clair où placer la table virtuelle et elle est placée partout. En conséquence, nous obtenons l'avertissement.

Vous trouverez même cette explication dans la documentation source de Clang .

Donc, spécifiquement à l'avertissement: vous obtiendrez l'avertissement lorsque toutes vos fonctions virtuelles appartiendront à l'une des catégories suivantes:

  1. inline est spécifié pour A::x() dans la définition de classe.

    struct A {
        inline virtual void x();
        virtual ~A() {
        }
    };
    void A::x() {
    }
    
  2. B :: x () est en ligne dans la définition de classe.

    struct B {
        virtual void x() {
        }
        virtual ~B() {
        }
    };
    
  3. C :: x () est purement virtuel

    struct C {
        virtual void x() = 0;
        virtual ~C() {
        }
    };
    
  4. (Appartient à 3.) Vous avez un destructeur virtuel pur

    struct D {
        virtual ~D() = 0;
    };
    D::~D() {
    }
    

    Dans ce cas, l'ordre pourrait être défini, car le destructeur doit être défini, néanmoins, par définition, il n'y a toujours pas de "première" unité de traduction.

Pour tous les autres cas, la fonction clé est la première fonction - virtuelle qui ne rentre pas dans l'une de ces catégories, et la table sera placée dans l'unité de traduction où la fonction clé est définie.

23
overseas

Pour un instant, oublions les fonctions virtuelles pures et essayons de comprendre comment le compilateur peut éviter d'émettre la vtable dans toutes les unités de traduction qui incluent la déclaration d'une classe polymorphe.

Lorsque le compilateur voit la déclaration d'une classe avec des fonctions virtuelles, il vérifie s'il existe des fonctions virtuelles qui sont uniquement déclarées mais non définies dans la déclaration de classe. S'il existe exactement une de ces fonctions, le compilateur sait avec certitude qu'elle doit être définie quelque part (sinon le programme ne sera pas lié), et émet la vtable uniquement dans l'unité de traduction hébergeant la définition de cette fonction. S'il existe plusieurs fonctions de ce type, le compilateur choisit l'une d'entre elles à l'aide de certains critères de sélection déterministes et - en ce qui concerne la décision de l'endroit où émettre la vtable - ignore les autres. La manière la plus simple de sélectionner une telle fonction virtuelle représentative unique est de prendre la première de l'ensemble candidat, et c'est ce que fait clang.

Ainsi, la clé de cette optimisation est de sélectionner une méthode virtuelle de telle sorte que le compilateur puisse garantir qu'il rencontrera une définition (unique) de cette méthode dans une unité de traduction.

Maintenant, que se passe-t-il si la déclaration de classe contient des fonctions virtuelles pures? Un programmeur peut fournir une implémentation pour une fonction virtuelle pure mais (s) il n'est pas obligé de! Les fonctions virtuelles pures n'appartiennent donc pas à la liste des méthodes virtuelles candidates parmi lesquelles le compilateur peut sélectionner la méthode représentative.

Mais il y a une exception - un destructeur virtuel pur!

Un destructeur virtuel pur est un cas particulier:

  1. Une classe abstraite n'a pas de sens si vous n'allez pas en dériver d'autres classes.
  2. Un destructeur de sous-classe appelle toujours le destructeur de la classe de base.
  3. Le destructeur d'une classe dérivant d'une classe avec un destructeur virtuel est automatiquement une fonction virtuelle.
  4. Toutes les fonctions virtuelles de toutes les classes, dont le programme crée des objets, sont généralement liées à l'exécutable final (y compris les fonctions virtuelles pouvant être prouvées restent inutilisés, mais cela nécessiterait une analyse statique du programme complet).
  5. Par conséquent, un destructeur virtuel pur doit avoir une définition fournie par l'utilisateur.

Ainsi, l'avertissement de Clang dans l'exemple de la question n'est pas conceptuellement justifié.

Cependant, du point de vue pratique, l'importance de cet exemple est minime, car un destructeur virtuel pur est rarement, voire pas du tout, nécessaire. Je ne peux pas imaginer un cas plus ou moins réaliste où un destructeur virtuel pur ne sera pas accompagné d'une autre fonction virtuelle pure. Mais dans une telle configuration, le besoin de pureté du destructeur (virtuel) disparaît complètement, car la classe devient abstraite en raison de la présence d'autres méthodes virtuelles pures.

16
Leon

J'ai fini par implémenter un destructeur virtuel trivial, plutôt que de le laisser pur virtuel.

Donc au lieu de

class A {
public:
    virtual ~A() = 0;
};

J'utilise

class A {
public:
    virtual ~A();
};

Implémentez ensuite le destructeur trivial dans un fichier .cpp:

A::~A()
{}

Cela épingle efficacement la vtable dans le fichier .cpp, au lieu de le produire en plusieurs unités de traduction (objets), et évite avec succès l'avertissement -Wweak-vtables.

En tant qu'effet secondaire de la déclaration explicite du destructeur, vous n'obtenez plus les opérations de copie et de déplacement par défaut. Voir https://stackoverflow.com/a/29288300/954 pour un exemple où ils sont redéclarés.

11
Ted Percival

Cela peut être résolu de trois manières.

  1. Utilisez au moins une fonction virtuelle qui n'est pas en ligne. Définir un destructeur virtuel est également correct dans la mesure où il ne s'agit pas d'une fonction en ligne.

  2. Désactivez l'avertissement comme indiqué ci-dessous.

    #pragma clang diagnostic Push#pragma clang diagnostic ignored "-Wweak-vtables"class ClassName : public Parent{...};#pragma clang diagnostic pop

  3. Utilisez uniquement des fichiers .h pour les déclarations de classe.
0
R.N.V.