C++ 未命名类C+中的静态数据成员+;
我在下面的链接中读到,未命名(匿名)类不应该包含静态数据成员。有人能告诉我原因吗 下面说的是 程序中只能有一个静态成员的定义。 未命名类、包含在未命名类中的类和本地类 类不能有静态数据成员C++ 未命名类C+中的静态数据成员+;,c++,C++,我在下面的链接中读到,未命名(匿名)类不应该包含静态数据成员。有人能告诉我原因吗 下面说的是 程序中只能有一个静态成员的定义。 未命名类、包含在未命名类中的类和本地类 类不能有静态数据成员 所有静态成员数据(如果使用ODR)必须在类/结构之外定义 struct Foo { static int d; }; int Foo::d = 0; 如果类/结构未命名,则无法在类之外定义成员 int ::d = 0; 无法用于定义未命名类的静态成员 C++17的更新 如果您能够使用C++17
所有
静态成员数据(如果使用ODR)必须在类/结构之外定义
struct Foo
{
static int d;
};
int Foo::d = 0;
如果类/结构未命名,则无法在类之外定义成员
int ::d = 0;
无法用于定义未命名类的静态成员
C++17的更新
如果您能够使用C++17或更高版本,您可以使用
static inline int d = 10;
这将允许在匿名类
/结构
中定义静态
成员变量
示例代码,用于证明无需在类定义之外定义静态
成员变量:
#include <iostream>
struct foo
{
static inline int d = 10;
};
int main()
{
auto ptr = &foo::d;
std::cout << *ptr << std::endl;
}
运行程序的输出:
10
感谢@Jean MichaëlCelerier对更新的建议。您确定标准实际上禁止这样做吗
如前所述,当您需要静态成员的实际定义时,问题就出现了。该语言不提供定义它的方法。在引用它时没有其他问题,因为我们可以从结构中或通过它的实例来执行它
但是,例如,GCC将接受以下内容:
static struct {
static int j;
} a;
int main() {
return a.j; // Here we actually refers to the static variable
}
但是它不能被链接,因为a.j
指的是一个未定义的符号(\u 0::j
),但是有一种方法可以绕过这个问题。通过在汇编程序中定义它或使用编译器扩展,您可以。例如添加行
int j asm("_ZN3._01jE") = 42;
我会让它工作的<代码> {Zn3..01JE</代码>是静态变量的实际名称,在这种情况下,未被损坏或未被损坏的名称可以直接用作标准C++中的标识符(但它可以通过GCC扩展或汇编程序)。
您可能已经意识到,这只适用于特定的编译器。其他编译器会以其他方式破坏名称(甚至做其他可能使该技巧根本不起作用的事情)
你真的应该质疑你为什么要使用这个技巧。如果你能用标准的方法来做这项工作,你很可能会选择这种方法。例如,您可以通过使用匿名命名空间来降低可见性,例如:
namespace {
static struct Fubar {
static int j;
} a;
Fubar::a = 0;
}
现在的代码> FuBar 并不是真正的匿名,但至少局限于翻译单元。
< P> C++被标准化时,未命名类不能有静态数据成员,因为无法定义/实例化它们。但是,C++11解决了这个问题,因为它添加了decltype
操作符:
struct {
static int j;
} a;
// Declare the static member of the unnamed struct:
int decltype(a)::j = 42;
int main() {
return a.j == 42 ? 0 : 1; // Use the static member
}
因此,原则上,可能存在带有静态数据成员的未命名类或结构。但是C++标准的制造者故意不允许这种语法,因为编译器不知道,它应该给哪个名字命名为<代码> DECKEYPE(A)::J< /COD>链接的东西。因此,大多数(所有?)编译器——包括当前版本的GCC在正常模式下——拒绝编译它
在-fpermissive
模式下,GCC-9和GCC-10接受此代码并对其进行良好编译。但是,如果a
的声明被移动到一个头文件中,该头文件包含在不同的源文件中,那么它们在链接阶段仍然会失败
因此,未命名类只能在单个翻译单元内使用。为了避免污染全局名称空间,只需将任何需要保持本地的内容放在匿名名称空间中。因此:
namespace {
struct whatever {
static int j;
} a;
int whatever::j = 42;
}
int main() {
return a.j == 42 ? 0 : 1;
}
编译很好,不会污染全局命名空间,甚至不会导致问题,如果名称whatever
与另一个头文件中的名称冲突。如果类没有名称,您将如何引用成员?@molbdnilo您可以从类内引用它,但问题在于,答案描述了它的实际定义需要从外部完成。@molbdnilostruct{int x;}a_变量&decltype(a_变量)::x
工作,因此没有理由decltype(a_变量)::静态数据成员代码>无法工作我真的希望这在实际生产代码中从未使用过。虽然它可能会工作,但如果您使用不同的编译器,您会遇到奇怪的错误。@HiGuy不,没有那么奇怪-您可能会得到一个未定义的符号,因为名称会有所不同。在某些情况下,在生产代码中使用特定于编译器的行为是可以接受的(IMHO)。然而,我无法给出使用这种特殊技巧的合理理由。在大多数情况下,生产代码仅与一个编译器链接,有时仅与一个编译器版本和一个平台链接。这完全可以。@g24l他并不是说要使用多个编译器/链接器来生成部署,而是说当您为多个平台编译包时,您将需要多个编译器。一个编译器漏掉的错误可能不会在其他编译器上漏掉,从而导致一系列复杂的交叉编译器问题需要解决。像这样做“时髦”的事情从来都不是“好的”-除非绝对没有其他方法来解决给定的问题,否则应该避免这样做(可能性很小)。@ZacHowland,嗯,可移植性并不总是一个你知道的要求。在C++17中,我们有静态内联,它消除了这个限制:struct Foo{static inline int d;}@Jean MichaëlCelerier,很高兴知道。您知道对于使用的ODRstatic
成员变量来说,这是否足够;链接器的任务是确保最后一个程序中只有一个d
实例。@Jean-MichaëlCelerier,谢谢你的更新。