C ++类名冲突

时间:2018-07-03 12:20:39

标签: c++

对于以下C ++代码,我得到了意外的行为。该行为已通过最新的GCC,Clang和MSVC ++进行了验证。要触发它,需要将代码拆分到几个文件中。

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

预期输出:

foo 1
bar 1

实际输出:

bar 4.94066e-324
bar 1

我希望发生的事情:

在foo.cc内,创建了一个在foo.cc中定义的X实例,并通过调用call()来调用foo.cc中的hook()实现。酒吧也一样。

实际发生的事情:

在foo.cc中创建了foo.cc中定义的X的实例。但是,在调用call时,它不会分派到foo.cc中定义的hook()而是分派到bar.cc中定义的hook()。这会导致损坏,因为钩子的参数仍然是int,而不是double。

可以通过将X的定义放在foo.cc中的X的定义而不是在bar.cc中的X定义中来解决该问题

最后是问题:没有关于此的编译器警告。 gcc,clang或MSVC ++都没有显示警告。按照C ++标准的定义,该行为是否有效?

这种情况似乎有点虚构,但它发生在现实世界中。我正在使用Rapidcheck编写测试,其中将要测试的单元上的可能操作定义为类。 大多数容器类具有类似的操作,因此在编写队列和向量的测试时,名称如“清除”,“推送”或“弹出”的类可能会出现几次。由于这些仅在本地需要,因此我将它们直接放在执行测试的源中。

2 个答案:

答案 0 :(得分:48)

该程序格式错误,因为它通过对类X具有两个不同的定义而违反了One-Definition Rule。因此,它不是有效的C ++程序。请注意,该标准专门允许编译器不诊断这种冲突。因此,编译器是一致的,但是程序不是有效的C ++,因此在执行时具有Undefined Behaviour(因此可能发生任何事情)。

答案 1 :(得分:25)

在不同的编译单元中,您有两个名称相同但不同的类X,导致程序格式错误,因为现在有两个符号相同。由于只能在链接期间检测到该问题,因此编译器无法(也不需要)报告此问题。

避免这种情况的唯一方法是放置任何不打算导出的代码 (尤其是所有尚未在头文件中声明的代码)放入anonymous or unnamed namespace

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

,并且等效于bar.cc。实际上,这是未命名名称空间的主要(唯一的)目的。

在实践中,简单地重命名类(例如fooXbarX)可能对您有用,但这不是一个稳定的解决方案,因为不能保证这些符号名称不会被在链接或运行时(现在或将来的某个时刻)加载的一些晦涩的第三方库。