c++ - 存在覆盖模糊函数时代码如何运行?
问题描述
当存在覆盖不明确的功能时,我无法完全理解代码结果。
我有一个库libMy
,其中包含两个类A
和B
.
代码显示如下
// A.h
#ifndef included_A_h
#define included_A_h
class A
{
public:
void print();
};
#endif
// A.cpp
#include "A.h"
#include <iostream>
void A::print()
{
std::cout << "A from library" << std::endl;
}
// B.h
#ifndef included_B_h
#define included_B_h
class A;
class B
{
public:
void printA(A &a);
};
#endif
// B.cpp
#include "B.h"
#include "A.h"
void B::printA(A &a)
{
a.print();
}
我有两个主要功能,它们可以用库生成两个可执行文件。
可以发现 Main*.cpp 看起来很奇怪。为什么需要看起来像这样在底部解释。
// MainUsingCPP.cpp
#include <iostream>
#define included_A_h
class A
{
public:
void print()
{
std::cout << "A from Main" << std::endl;
}
};
#include "B.cpp" // note: using B.cpp here
int main()
{
A obj_a;
B obj_b;
obj_b.printA(obj_a);
return 0;
}
// MainUsingH.cpp
#include <iostream>
#define included_A_h
class A
{
public:
void print()
{
std::cout << "A from Main" << std::endl;
}
};
#include "B.h" // note: using B.h here
int main()
{
A obj_a;
B obj_b;
obj_b.printA(obj_a);
return 0;
}
使用后续行,我们可以编译库,并生成可执行文件。
# generate library
g++ -c A.cpp
g++ -c B.cpp
ar -crv libMy.a A.o B.o
# compile case CPP
g++ MainUsingCPP.cpp -L . -lMy -o MainUsingCPP
# compile case H
g++ MainUsingH.cpp -L . -lMy -o MainUsingH
并运行可执行文件,结果如下图
./MainUsingH
A from library
./MainUsingCPP
A from Main
我的问题是:
(1)为什么代码可以编译?
考虑MainUsingCPP.cpp
到库,A 类被重新定义。所以我们有两个A::print()
版本。一个MainUsingCPP.cpp
来自图书馆,另一个来自图书馆。在这些阶段,A::print()
是模棱两可的。为什么代码可以编译?链接器如何区分它们?链接器如何决定它需要使用哪个版本的函数?
(2)如何理解结果?
为什么导致两个可执行文件不同?为什么链接器A::print()
从 library 中MainUsingH.cpp
选择并A::print()
从 Main in 中选择MainUsingCPP.cpp
为什么 Main.cpp 看起来很奇怪
A
是一个类并且B
是A
的用户。在MainUsingCPP.cpp
中,A
的功能似乎可以重新定义。也就是说,A
即使 A 没有虚函数,也可以模拟 is 进行单元测试!
更多可以看到 Peter Dotchev 在fake/mock nonvirtual C++ methods中的回答
谢谢你的时间!
解决方案
(1)为什么代码可以编译?
单一定义规则说
在任何一个翻译单元中只允许对任何变量、函数、类类型、枚举类型、...或模板进行一种定义
这是满足的,因为每个目标文件对应一个不同的翻译单元。
这么多的编译(翻译单元到目标文件) - 现在用于链接:
每个非内联函数或变量 ... 的一个且只有一个定义需要出现在整个程序中(包括任何标准和用户定义的库)。编译器不需要诊断这种违规行为,但违反它的程序的行为是未定义的。
所以你的程序行为是未定义的,但编译器不需要告诉你,甚至不需要自己确定。
(2)如何理解结果?
不要试图理解未定义的行为,它是未定义的。
如果您想了解您的特定编译器对损坏的代码做了什么,那么让它为您扩展两个主要的翻译单元(-E
用于 GCC)。但是,这实际上是关于你的编译器而不是语言的问题,因为语言没有明确定义这种情况。
推荐阅读
- javascript - 地图覆盖不显示。如何显示叠加层?
- javascript - 我有一个更新后返回列表的问题,它将进入初始页面
- android-studio - Android Studio 3.3 在布局预览中以不同语言显示 textview 文本
- scala - Jupyter Notebook (Scala, kernel - Apache Toree) with Vegas, 图表不显示数据
- mpeg-dash - 在 MP4Box 中为每个表示分离初始段
- javascript - 如何从原型函数中仅获取参数值
- javascript - Xrm.WebApi.online.execute 帮助创建通知
- macos - 'Connection' 对象在 macOS 上没有属性 'ssh' ansible?
- java - 为什么模式正则表达式 [0-9a-z]{6} 不替换?
- python - 链接 python app docker 和 postgress docker