我有一个抽象类
namespace AComp
{
class A
{
public:
virtual void func() = 0;
virtual ~A();
};
A::~A() { }
}
我还有一个抽象子类,它不提供纯虚拟成员的实现
namespace BComp
{
class B : public AComp::A
{
public:
virtual void func() override = 0;
virtual ~B() override;
};
B::~B() { }
}
然后我有一个提供实现的子类
namespace CComp
{
class C : public BComp::B
{
public:
virtual void func() override;
};
void C::func() { }
}
最后,在我使用 C 类的班级中
AComp::A * instanceOfA = new CComp::C();
链接器抛出以下错误
对“B 的 vtable”的未定义引用
我认为这真的很愚蠢,但我不明白问题是什么。
此代码在 ESP-32 微处理器上运行,全部使用 VisualCode 和 ESP-IDF 框架编写,其中:A 在组件 Acomp 中,B 在组件 Bcomp 中,C 在组件 Ccomp 中,调用代码在主应用程序
我已经通过几个在线编译器运行了代码,似乎没有问题,但是我确实遇到了链接器错误
编译器信息
-- The C compiler identification is GNU 11.2.0
-- The CXX compiler identification is GNU 11.2.0
-- The ASM compiler identification is GNU
-- Found assembler: A:/espressif/.espressif/tools/xtensa-esp32-elf/esp-2022r1-11.2.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc.exe
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: A:/espressif/.espressif/tools/xtensa-esp32-elf/esp-2022r1-11.2.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: A:/espressif/.espressif/tools/xtensa-esp32-elf/esp-2022r1-11.2.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-g++.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Building ESP-IDF components for target esp32
@BenVoigt 发现了问题。
最直接的原因是“编译器没有生成 vtable,因为它认为其他人(在另一个文件上运行的编译器)会这样做”。有六件事会影响这种行为,因此很难将其中任何一个标记为“根本原因”。请仔细检查
中的每个其他虚拟函数是否都有定义,并且该定义已链接。链接的解释 (stackoverflow.com/a/26928723/103167) 表明问题是 (a)首先(按源代码顺序)声明的虚函数或 (b) 从B
继承的虚函数,在A
中不是纯函数。B
我将代码按原样放置在新项目中,并且编译和链接没有错误。这让我回到项目中的代码。
BComp::B
和CComp::C
都有额外相关的补充。
namespace BComp
{
class B : public AComp::A
{
protected:
virtual void func1();
virtual void func2();
public:
virtual void func() override = 0;
virtual ~B() override;
};
B::~B() { }
}
namespace CComp
{
class C : public BComp::B
{
public:
virtual void func() override;
};
void C::func() { }
void C::func1() { }
void C::func2() { }
}
BComp 还声明了一些其他非纯虚拟成员,在子类
CComp::C
中实现。对于 C#、VB.Net、Java 等用户来说,这看起来很自然;然而,正如@BenVoigt 指出的:
如果声明虚拟成员函数,则必须在同一个类中提供定义,或者使其变得纯并在子类中提供定义。子类中的定义本身还不够好。
因此,解决方案是遵守 C++ 编程规则,与目标 (ESP32)、编译器或链接器无关。
解决方案是让
BComp::B
的虚拟成员变得纯粹:
namespace BComp
{
class B : public AComp::A
{
protected:
virtual void func1() = 0;
virtual void func2() = 0;
public:
virtual void func() override = 0;
virtual ~B() override;
};
B::~B() { }
}
@BenVoigt 的总结:
通常,链接器会为 B 中没有定义的虚拟函数提供未解决的外部错误。但错误消息将是“未解析的
在B::bar()
中引用”。由于您还缺少 vtable,因此没有看到任何更有意义的错误。因为工具链很愚蠢,并且生成了程序员永远不会看到的错误,而不是关于真正问题的有意义的消息[...]。 [...] 现在您已经了解了重要部分(vtable of B
不是,A
不是,func()
中声明并在B
中实现的虚函数是)。C