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您可以从类内引用它,但问题在于,答案描述了它的实际定义需要从外部完成。@molbdnilo
struct{int x;}a_变量&decltype(a_变量)::x
工作,因此没有理由
decltype(a_变量)::静态数据成员无法工作我真的希望这在实际生产代码中从未使用过。虽然它可能会工作,但如果您使用不同的编译器,您会遇到奇怪的错误。@HiGuy不,没有那么奇怪-您可能会得到一个未定义的符号,因为名称会有所不同。在某些情况下,在生产代码中使用特定于编译器的行为是可以接受的(IMHO)。然而,我无法给出使用这种特殊技巧的合理理由。在大多数情况下,生产代码仅与一个编译器链接,有时仅与一个编译器版本和一个平台链接。这完全可以。@g24l他并不是说要使用多个编译器/链接器来生成部署,而是说当您为多个平台编译包时,您将需要多个编译器。一个编译器漏掉的错误可能不会在其他编译器上漏掉,从而导致一系列复杂的交叉编译器问题需要解决。像这样做“时髦”的事情从来都不是“好的”-除非绝对没有其他方法来解决给定的问题,否则应该避免这样做(可能性很小)。@ZacHowland,嗯,可移植性并不总是一个你知道的要求。在C++17中,我们有静态内联,它消除了这个限制:struct Foo{static inline int d;}@Jean MichaëlCelerier,很高兴知道。您知道对于使用的ODR
static
成员变量来说,这是否足够;链接器的任务是确保最后一个程序中只有一个
d
实例。@Jean-MichaëlCelerier,谢谢你的更新。