C++类名冲突

C++ class name collision

本文关键字:冲突 C++      更新时间:2023-10-16

对于以下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;
};

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

酒吧.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 中定义的 X 实例是在 foo(( 中创建的。但是当调用调用时,它不是调度到 foo.cc 中定义的 hook((,而是调度到 bar.cc 中定义的 hook((。这会导致损坏,因为钩子的参数仍然是一个整数,而不是一个双精度。

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

所以最后的问题是:没有编译器对此的警告。gcc、clang 或 MSVC++ 都没有对此发出警告。根据C++标准的定义,该行为是否有效?

这种情况似乎有点建构,但它发生在现实世界的场景中。我正在使用快速检查编写测试,其中对要测试的单元的可能操作定义为类。 大多数容器类都有类似的操作,因此在为队列和向量编写测试时,名称为"清除"、"推送"或"Pop"的类可能会出现多次。由于这些仅在本地需要,因此我将它们直接放在执行测试的源中。

该程序格式不正确,因为它违反了单定义规则,因为它对类X有两个不同的定义。因此,它不是一个有效的C++程序。请注意,该标准特别允许编译器不诊断此冲突。因此,编译器是符合要求的,但程序C++无效,因此在执行时具有未定义的行为(因此任何事情都可能发生(。

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

避免此类事情的唯一方法是任何不打算导出的代码(特别是尚未在头文件中声明的所有代码(放入匿名或未命名的命名空间中:

#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( 在实践中可能适合您,但不是稳定的解决方案,因为无法保证这些符号名称不会被在链接或运行时(现在或将来的某个时间点(加载的某些晦涩的第三方库使用。