首页 > 解决方案 > 存在覆盖模糊函数时代码如何运行?

问题描述

当存在覆盖不明确的功能时,我无法完全理解代码结果。

我有一个库libMy,其中包含两个类AB.

代码显示如下

// 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是一个类并且BA的用户。在MainUsingCPP.cpp中,A的功能似乎可以重新定义。也就是说,A即使 A 没有虚函数,也可以模拟 is 进行单元测试!

更多可以看到 Peter Dotchev 在fake/mock nonvirtual C++ methods中的回答

谢谢你的时间!

标签: c++compilationlinkerg++ambiguous

解决方案


(1)为什么代码可以编译?

单一定义规则

在任何一个翻译单元中只允许对任何变量、函数、类类型、枚举类型、...或模板进行一种定义

这是满足的,因为每个目标文件对应一个不同的翻译单元。

这么多的编译(翻译单元到目标文件) - 现在用于链接:

每个非内联函数或变量 ... 的一个且只有一个定义需要出现在整个程序中(包括任何标准和用户定义的库)。编译器不需要诊断这种违规行为,但违反它的程序的行为是未定义的。

所以你的程序行为是未定义的,编译器不需要告诉你,甚至不需要自己确定。


(2)如何理解结果?

不要试图理解未定义的行为,它是未定义的。

如果您想了解您的特定编译器对损坏的代码做了什么,那么让它为您扩展两个主要的翻译单元(-E用于 GCC)。但是,这实际上是关于你的编译器而不是语言的问题,因为语言没有明确定义这种情况。


推荐阅读