必须定义所有静态类方法,即使不使用

Must all static class methods be defined, even when not used?

本文关键字:类方法 静态类 定义 静态      更新时间:2023-10-16

所以,我遇到了一个问题,我不确定这是语言问题还是编译器/GCC问题。

TL;DR-是否需要我定义类中的所有静态方法,即使这些静态方法从未被应用程序调用(即链接器无论如何都可以合法删除)?

我有一个库类,它实现了微控制器中UART的设备驱动程序。因为我不希望多个UART对象指向同一资源,所以每个UART对象都是一个单例,使用几种GetInstance()方法之一检索,设备中的每个UART实例(UART0、UART1等)都有一个方法。每个UART实例需要有两个FIFO(Tx和Rx)用于存储。应用程序需要明确确定每个FIFO的大小,并在UART对象实例化时进行分配(理想情况下)。因此,我也有几个静态GetStorage()方法,同样,每个UART一次。

我已经为我的概念验证创建了一些精简的代码。这是static_instance.h:

#ifndef STATIC_INSTANCE_H_
#define STATIC_INSTANCE_H_
#ifdef __cplusplus
#include <vector>
namespace foo {
class Uart {
public:
/* Retrieve a singleton instance, using lazy static initialization.  Note
* that not all instances will be present for a given device. */
static Uart& Uart1GetInstance(void);
static Uart& Uart2GetInstance(void);
static Uart& Uart3GetInstance(void);
/* Does something. */
void DoSomething(void) { ++counter; }
private:
/* Structure for the storage that each static Uart instance requires. */
struct Storage {
Storage(std::vector<char>& vector)
: my_vector_(vector) { }
std::vector<char>& my_vector_;   // Buffer for data.
};
/* Instantiate object using provided register base and FIFO structures. */
Uart(int instance, Storage& storage)
: instance_(instance), storage_(storage) { }
~Uart() { }
/* Retrieves the storage required for the static Uart object instances.
* These methods are NOT implemented in static_instance.cc, but must be
* implemented in the application code, only for those Uart instances
* that are invoked in the application. */
static Storage& Uart1GetStorage(void);
static Storage& Uart2GetStorage(void);
static Storage& Uart3GetStorage(void);
int const instance_;    // Instance number of this object.
Storage& storage_;      // Allocated storage for this object.
int counter = 0;        // Dummy counter.
};
} // namespace foo
#endif  // __cplusplus
#endif

下面是static_instance.cc:

#include <static_instance.h>
namespace foo {
Uart& Uart::Uart1GetInstance(void) {
static Uart uart(1, Uart1GetStorage());
return uart;
}
Uart& Uart::Uart2GetInstance(void) {
static Uart uart(2, Uart2GetStorage());
return uart;
}
Uart& Uart::Uart3GetInstance(void) {
static Uart uart(3, Uart3GetStorage());
return uart;
}
} // namespace foo

其思想是,您只为实际需要的UART实例调用GetInstance(),然后只为该UART实例定义GetStorage()。(在这个例子中,我只定义了一个缓冲区,并使用std::vector<char>作为替代。)此外,存储方法由应用程序来定义,因为每个应用程序都对给定UART的缓冲区需要有多大有自己的要求。)以下是main.cc中实例化UART2:的代码片段

namespace foo {
Uart::Storage& Uart::Uart2GetStorage(void) {
static std::vector<char> rx_vector(256, 0);
static Uart::Storage storage(rx_vector);
return storage;
}
static foo::Uart& uart_ = foo::Uart::Uart2GetInstance();
void wibble(void) {
uart_.DoSomething();
}
} // namespace foo

现在,我正在为这个芯片开发一个早期的应用程序,使用早期的IDE(Kinetis Design Studio v3.2.0版),它使用GCC 4.8.4,编译和链接没有错误

但恩智浦已经否决了KDS用于另一个工具链(MCUXpresso 10.0),该工具链使用GCC 5.4.1,并且使用完全相同的代码,这一次我得到了两个链接器错误

./source/static_instance.o: In function 'foo::Uart::Uart1GetInstance()':
../source/static_instance.cc:5: undefined reference to 'foo::Uart::Uart1GetStorage()'
./source/static_instance.o: In function 'foo::Uart::Uart3GetInstance()':
../source/static_instance.cc:13: undefined reference to 'foo::Uart::Uart3GetStorage()'

我不知道为什么链接器关心UART1和UART3的GetStorage()方法没有定义,因为我在应用程序中没有为UART1或UART3调用GetInstance(),因此也从未调用相应的GetStorage()方法。

我的问题是…C++11是否要求在我的可执行文件中定义所有三种存储方法?也就是说,GCC 4.8.4是否让我逃脱了一些我不该做的事情?或者这是我需要切换的GCC 5.4选项,以允许我从类中删除未使用的静态成员?

如果答案是"不管怎样,你都必须定义它们",那么我会定义它们,或者设计其他允许的方式。如果答案是"应该没问题",并且我不能在命令行上设置任何选项来让GCC 5.4执行此操作,那么我将采取下一步行动,在NXP论坛中报告一个错误。谢谢

TL;DR-我是否需要定义类中的所有静态方法,即使应用程序从未调用过这些静态方法

您可能需要定义这样的静态方法,即使应用程序从未调用过它们。

但是,一般来说,如果没有使用odr(一个定义规则),您可能不需要这样做。对于静态成员函数,这相当于

一个函数,其名称显示为潜在的求值表达式

差异很微妙。我希望这能证明:

if(false)
function();

function从不被调用,而是出现在可能求值的表达式中,因此是odr使用的,因此必须定义。


我的问题是…C++11是否要求我在可执行文件中定义所有三种存储方法?

是。它们都出现在可能求值的表达式中,因此是odr使用的,因此必须定义。

我使用。。。GCC 4.8.4,并且编译和链接没有错误。

Odr违规具有未定义的行为,这解释了为什么在其他工具链中没有收到错误/警告。

[basic.def.odr]

  1. 每个程序都应包含odr中使用的每个非内联函数或变量的一个定义该程序在被丢弃的语句之外(9.4.1);无需诊断

这是一个相对较新的标准草案,但所有版本都包含类似的语句。

";不需要诊断";子句允许编译器接受您的程序,即使它违反了规则。在一段不可访问的代码中使用odr正是合理的情况:编译器可能会也可能不会优化掉包含违规调用的死代码。您的程序仍处于违规状态,另一个实现可能会拒绝它。