Visual C++跨编译单元同名类方法被误调用问题咨询
问题分析:同名类跨编译单元的诡异行为
我有两个无关联头文件的独立编译单元,核心代码如下:
单元a.cpp
#include <algorithm> #include <vector> class my_predicate { const std::vector<int>& vec; public: my_predicate(const std::vector<int>& container) : vec(container) { } bool operator() (size_t idx1, size_t idx2) { return vec[idx1] < vec[idx2]; } }; int main() { std::vector<int> v1,v2; v1.resize(10); v2.resize(10); std::sort(v1.begin(), v1.end(), my_predicate(v2)); }
单元b.cpp
#include <algorithm> #include <vector> #include <iostream> class my_predicate { const std::vector<char>& vec; public: my_predicate(const std::vector<char>& container) : vec(container) { } bool operator() (size_t idx1, size_t idx2) { std::cout << "Why the operator from b.cpp is called?" << std::endl; return vec[idx1] < vec[idx2]; } }; void bar() { std::vector<char> v1, v2; std::sort(v1.begin(), v1.end(), my_predicate(v2)); }
两个my_predicate类的核心区别在于成员引用的容器类型:vector<int>和vector<char>。
异常现象
理论上a.cpp不应知晓b.cpp中的my_predicate类,但执行a.cpp中main的sort时出现异常:
- 调用了a.cpp中
my_predicate的正确构造函数; - 排序时却调用了b.cpp中
my_predicate的operator()方法。
补充细节
- 该现象在Debug模式下也能复现,此时已关闭全程序优化;
- 仅当b.cpp中有
my_predicate的调用(哪怕是未使用的bar函数)时才会出现此问题; - 使用VS重命名工具修改a.cpp中的
my_predicate时,工具会同时修改两个文件。
疑问:这是类定义全局可见的特性还是编译器Bug?
原因解析:违反ODR单一定义规则
这不是编译器Bug,而是代码违反了C++标准中的ODR(单一定义规则)。
ODR明确规定:在整个程序中,同一个名字的类(或其他实体)如果在多个编译单元中定义,那么这些定义必须完全一致。如果不一致,程序行为属于未定义行为——编译器和链接器不会主动报错,但运行时会出现各种不可预期的诡异行为,就像这里遇到的情况。
具体到这段代码:
- a.cpp和b.cpp都在全局作用域定义了名为
my_predicate的类,但两个类的成员变量类型、operator()的实现均不相同,属于不一致的定义; - 链接器在合并编译单元时,会将全局作用域中同名的符号(包括类的成员函数)进行关联。由于两个类名字相同,链接器错误地将a.cpp中创建的
my_predicate对象,与b.cpp中的operator()函数绑定,导致运行时调用错误的方法; - VS重命名工具同时修改两个文件,只是IDE基于文本搜索的全局替换特性,和C++语言本身的规则无关。
解决办法
- 给两个类设置不同的名称,从根源避免全局作用域的名字冲突;
- 将类放入各自独立的命名空间(
namespace)中,即使名字相同,不同命名空间下的类会被视为完全不同的实体; - 如果类仅在当前编译单元内使用,用匿名命名空间包裹(C11及以后推荐),或者将类声明为
static(C03及以前写法),限制其作用域仅在当前编译单元内。
内容的提问来源于stack exchange,提问作者Damir Tenishev
相关产品推荐
相关产品推荐

