可以在堆上实例化虚拟对象,但不能在堆栈上实例化吗?

Can instantiate a virtual object on the heap, but not on the stack?

首先让我说我来自 Java 背景,所以如果我在这里犯了一些愚蠢的错误,请原谅我....

我正在编写一个 C++ 库,我希望它与 Arduino 和 RaspberryPi 兼容。大多数代码只是干净的标准 C++,但一些函数是特定于硬件的(例如 GPIO 调用)。来自 Java 背景,我想,我将只创建一个 HAL 接口来定义那些特定于硬件的函数,然后我可以为 RPi 和 Arduino 创建该接口的实现,只是为了找出该接口不是 C++ 中的东西,你必须改用虚拟方法。 请在问题底部查看我的源文件。

但是,当我真正去使用这个的时候,我发现了一个很奇怪的事情:在堆上使用HAL可以编译,但是在栈上使用它却不行

//THIS DOES NOT COMPILE
MyLibraryHAL hal = HAL_RaspberryPi();
hal.someHwSpecificFunction(65);

//BUT THIS DOES
MyLibraryHAL* hal = new HAL_RaspberryPi();
hal->someHwSpecificFunction(65);

错误是:
src.ino:67: undefined reference to MyLibraryHAL::someHwSpecificFunction(unsigned char).

这是为什么?这只是虚拟方法如何工作的一个特性吗?或者我在这里缺少一些概念?


MyLibraryHAL.h

#include <stdint.h>

#ifndef MyLibraryHAL_h
#define MyLibraryHAL_h

class MyLibraryHAL
{
    public:
        virtual void someHwSpecificFunction(uint8_t param);
};

#endif

HAL_Arduino.h

#include <stdint.h>
#include "MyLibraryHAL.h"

#ifndef HAL_Arduino_h
#define HAL_Arduino_h

class HAL_Arduino : public MyLibraryHAL
{
    public:
        void someHwSpecificFunction(uint8_t param);
};

#endif

HAL_Arduino.cpp

#include <stdint.h>
#include "HAL_Arduino.h"

void HAL_Arduino::someHwSpecificFunction(uint8_t param)
{
    //do things
}

HAL_RaspberryPi.h

#include <stdint.h>
#include "MyLibraryHAL.h"

#ifndef HAL_RaspberryPi_h
#define HAL_RapsberryPi_h

class HAL_RaspberryPi : public MyLibraryHAL
{
    public:
        void someHwSpecificFunction(uint8_t param);
};

#endif

HAL_RaspberryPi.cpp

#include <stdint.h>
#include "HAL_RaspberryPi.h"

void HAL_RaspberryPi::someHwSpecificFunction(uint8_t param)
{
    //do things
}

没有指针,您将 HAL_RaspberryPi 复制到 MyLibraryHAL 实例,然后调用超类(未定义)上的方法。

所以是的,使用指针,但您应该使用 std::shared_ptr 或 std::unique_ptr 而不是原始指针。

你/可以/通过使你的堆栈变量成为一个引用来做到这一点:

Base& base = Derived(args);

但实际上它并没有给你带来很多好处,因为引用只能在它被声明的地方赋值,所以你能做的避免构造的最好方法是:

Base& base = Do1 ? Derived1(args) : Derived2(args);

您必须非常注意参考文献的生命周期延长规则。随心所欲:

https://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary

声明:

MyLibraryHAL hal = HAL_RaspberryPi();

切片 HAL_RaspberryPi 对象,因此只剩下一个 MyLibraryHAL 对象。参见 What is object slicing?

您需要指针或引用才能在运行时进行多态调用。这就是您的堆示例有效的原因。参见 Why doesn't polymorphism work without pointers/references?。您可以对在堆栈上实例化的对象使用多态性,但您仍然需要 pointer/reference 才能进行调用,例如:

HAL_RaspberryPi pi;
MyLibraryHAL &hal = pi;
hal.someHwSpecificFunction(65);
HAL_RaspberryPi pi;
MyLibraryHAL *hal = &pi;
hal->someHwSpecificFunction(65);

至于链接器的"undefined reference"错误,你实际上没有实现MyLibraryHALclass中的someHwSpecificFunction()方法,您只是 声明了 它,因此在 MyLibraryHAL 对象上尝试调用它时出错。如果不想提供默认实现,可以将方法声明为 "pure abstract" 方法:

class MyLibraryHAL {
public:
    virtual void someHwSpecificFunction(uint8_t param) = 0;
};

纯抽象方法必须在后代 classes 中重写。 class 只包含纯抽象方法,没有其他任何东西是您在其他语言中可以得到的最接近 "interface" 的东西。