48 votes

Collision de nom de classe C ++

Pour le code C++ suivant, je reçois un comportement inattendu. Le comportement a été vérifiée avec une version récente de GCC, Clang et MSVC++. Pour la déclencher, il est nécessaire de diviser le code entre plusieurs fichiers.

def.h

#pragma once

template<typename T>
struct Base
{
    void call() {hook(data);}
    virtual void hook(T& arg)=0;
    T data;
};

foo.h

#pragma once
void foo();

foo.cc

#include "foo.h"
#include <iostream>
#include "def.h"

struct X : Base<int>
{
    virtual void hook(int& arg) {std::cout << "foo " << arg << std::endl;}
};


void foo()
{
    X x;
    x.data=1;
    x.call();
}

bar.h

#pragma once
void bar();

bar.cc

#include "bar.h"

#include <iostream>
#include "def.h"

struct X : Base<double>
{
    virtual void hook(double& arg) {std::cout << "bar " << arg << std::endl;}
};


void bar()
{
    X x;
    x.data=1;
    x.call();
}

main.cc

#include "foo.h"
#include "bar.h"

int main()
{
    foo();
    bar();
    return 0;
}

Résultat attendu:

foo 1
bar 1

De sortie réelle:

bar 4.94066e-324
bar 1

Ce que j'ai prévu d'arriver:

À l'intérieur de foo.cc une instance de X définie à l'intérieur de foo.cc est et par l'appelant (appel), la mise en œuvre d'un crochet() à l'intérieur foo.cc est appelé. De même pour la barre.

Ce qui se passe réellement:

Une instance de X tel que défini dans foo.cc est faite dans foo(). Mais lors de l'appel téléphonique, il distribue à ne pas accrocher() définie dans foo.cc mais à crochet() définie dans bar.cc. Cela conduit à la corruption, comme l'argument de crochet est toujours un int, pas un double.

Le problème peut être résolu en ajoutant la définition de X dans foo.cc dans un autre espace de noms que de définition de X dans bar.cc

Donc, finalement, à la question: Il n'y a pas de compilateur d'avertissement à ce sujet. Ni gcc, ni clang ou MSVC++ n'a même afficher un avertissement à ce sujet. C'est qu'un comportement valable que définie selon la norme C++?

La situation semble s'être un peu construit, mais il s'est passé dans un scénario réel. J'étais à écrire des essais avec rapidcheck, où les actions possibles sur une unité à tester sont définies comme des classes. La plupart des classes de conteneurs ont des actions similaires, de sorte que lors de l'écriture de tests pour une file d'attente et un vecteur de classes, avec des noms comme "Clair", "Push" ou "Pop" peut venir plusieurs fois. Comme ce sont seulement tenus localement, je les ai mis directement dans les sources où les tests sont exécuter.

48voto

Angew Points 53063

Le programme est Mal formé, car elle contrevient à l' Une Définition de la Règle par le fait d'avoir deux définitions différentes pour la classe X. Il n'est donc pas valide programme en C++. Notez que la norme autorise spécifiquement les compilateurs de ne pas diagnostiquer cette violation. Alors que les compilateurs sont conformes, mais le programme n'est pas valide en C++ et en tant que telle a un Comportement indéterminé lors de l'exécution (et donc tout peut arriver).

25voto

Walter Points 7554

Vous avez deux noms identiques, mais différentes classes d' X dans les différentes unités de compilation, rendu le programme mal formé, comme il y a maintenant deux symboles avec le même nom. Depuis, le problème ne peut être détecté lors de la liaison, les compilateurs ne sont pas en mesure (et non obligatoire) de faire rapport de ce.

La seule façon d'éviter ce genre de chose, est de mettre tout le code qui n'est pas destiné à être exporté (en particulier l'ensemble du code qui n'a pas été déclarée dans un fichier d'en-tête) dans un anonyme ou sans nom d'espace de noms:

#include "foo.h"
#include <iostream>
#include "def.h"

namespace {
    struct X : Base<int>
    {
        virtual void hook(int& arg) {std::cout << "foo " << arg << std::endl;}
    };
}

void foo()
{
    X x;
    x.data=1;
    x.call();
}

et, de manière équivalente, pour bar.cc. En fait, c'est le principal (le seul?) le but de sans nom espace de noms.

Il suffit de le re-nommage de vos cours (par exemple, fooX et barX) peut fonctionner pour vous dans la pratique, mais n'est pas une solution stable, car il n'y a aucune garantie que ces symboles ne sont pas utilisés par quelque obscure bibliothèque tierce, chargé de la liaison ou de l'exécution (maintenant ou dans l'avenir).

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