C++ 多态性不适用于 ESP-IDF

问题描述 投票:0回答:1

我有一个抽象类

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
c++ polymorphism virtual abstract vtable
1个回答
1
投票

@BenVoigt 发现了问题。

最直接的原因是“编译器没有生成 vtable,因为它认为其他人(在另一个文件上运行的编译器)会这样做”。有六件事会影响这种行为,因此很难将其中任何一个标记为“根本原因”。请仔细检查

B
中的每个其他虚拟函数是否都有定义,并且该定义已链接。链接的解释 (stackoverflow.com/a/26928723/103167) 表明问题是 (a)首先(按源代码顺序)声明的虚函数或 (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 of B
中引用”。由于您还缺少 vtable,因此没有看到任何更有意义的错误。因为工具链很愚蠢,并且生成了程序员永远不会看到的错误,而不是关于真正问题的有意义的消息[...]。 [...] 现在您已经了解了重要部分(
A
不是,
func()
不是,
B
中声明并在
C
中实现的虚函数是)。

© www.soinside.com 2019 - 2024. All rights reserved.