在C ++中声明静态C结构实例

我正在编写一个代码生成器,实际上是一个数据生成器,它将生成这种forms的数据结构(显然,实际的数据结构要复杂得多):

typedef struct Foo { int a; struct Foo* foo; } Foo; extern Foo f1; extern Foo f2; Foo f1 = {1, &f2}; Foo f2 = {2, &f1}; 

这对于我尝试过的所有C和C ++编译器都是可移植的。

我想将这些struct实例声明为static,以免污染全局变量空间,如:

 typedef struct Foo { int a; struct Foo* foo; } Foo; static Foo f1; static Foo f2; static Foo f1 = {1, &f2}; static Foo f2 = {2, &f1}; 

虽然这适用于gcc和可能所有C编译器,但上面的代码不适用于C ++编译器并导致编译错误:

 error: redefinition of 'Foo f1' error: 'Foo f1' previously declared 

我理解为什么在C ++中发生这种情况。 是否有一个简单的解决方法,不涉及在运行时使用代码来实现可移植到所有C ++编译器的相同效果,而无需使用C编译器来编译某些文件?

这应该使用C或C ++进行编译,并为您提供相同的名称,以便在两个编译器中访问相同的内容。

 #ifdef __cplusplus namespace // use anonymous namespace to avoid poluting namespace. { struct StaticFoos { static Foo f1; static Foo f2; }; Foo StaticFoos::f1 = {1, &StaticFoos::f2}; Foo StaticFoos::f2 = {2, &StaticFoos::f1}; } static const &Foo f1 = StaticFoos::f1; static const &Foo f2 = StaticFoos::f2; #else static Foo f1 = {1, &f2_}; static Foo f2 = {1, &f1_}; #endif 

现在在C和C ++中,您可以访问f1f2

这似乎与Josh的答案有类似的效果,但复杂程度较低:

 #ifdef __cplusplus namespace { extern Foo f1; extern Foo f2; Foo f1 = {1, &f2}; Foo f2 = {2, &f1}; } #else static Foo f1; static Foo f2; Foo f1 = {1, &f2}; Foo f2 = {2, &f1}; #endif 

当为C ++编译时,f1和f2的extern定义在具有外部可链接符号的目标文件中公开; 但是,因为它们位于匿名命名空间内,所以符号会被破坏,以至于它们不会与来自另一个翻译单元的符号冲突。

使用宏魔法你可以设置,所以只有一个地方f1和f2被声明和定义,但如果这是机械生成的,那么可能没有太多理由这样做。

就像是:

 #ifdef __cplusplus #define START_PRIVATES namespace { #define END_PRIVATES } #define PRIVATE extern #else #define START_PRIVATES #define END_PRIVATES #define PRIVATE static #endif START_PRIVATES PRIVATE Foo f1; PRIVATE Foo f2; Foo f1 = {1, &f2}; Foo f2 = {2, &f1}; END_PRIVATES 

您要避免的是被称为静态初始化订单Fiasco 。 您将很好地使用函数,并使用默认值初始化单个对象,然后重置成员指针。

您的代码示例意味着完全不同的东西。 你将不得不重新审视。 第一个成功,因为你有一个对象的定义和另一个对象的声明。 这适用于C和C ++。

 extern Foo f1; 

这是一个宣言和一个暂定的定义。

 static Foo f1; 

这是struct Foo类型的对象f1的声明和定义。

 static Foo f2; 

同上。

 static Foo f1 = {1, &f2}; 

这是重新定义。 您违反了一个定义规则 ,该规则说明,翻译单元中必须有一个且只有一个符号定义。 除此之外,您可以拥有多个定义,但当然每个这样的事件都必须具有相同的语法和语义。

 static Foo f2 = {2, &f1}; 

同上。

 extern Foo fn; /* some code */ extern Foo fn; /* some more ... */ Foo fn; /* finally a definition */ 

这很好,因为可以有多个暂定声明。

您不能转发声明对象,只能转发类型。 外部解决方案是正确的解决方案。 或者,如果您确实需要避免全局命名空间污染,请将它们设置为静态并使用您在其他所有命令之前调用的函数对其进行初始化。

编辑: Michael Burr在评论中提到了原因,我想我会把它添加到post中:

@dirkgently:它是有效的C,因为C标准说:“在一个翻译单元中,具有内部链接的标识符的每个声明表示相同的对象或function”。

C ++没有这样的规则。

编辑:

正如另一篇文章所述。 您也可以使用匿名命名空间来限制变量的范围。 只需将名称空间包装在#ifdef __cplusplus ,你应该好好去。

我遇到过这个问题。 限制是令人沮丧的,我没有看到任何理由为什么C ++与C的这种无偿的不兼容性。

我的解决方案是使用静态函数 – 你可以转发声明 – 它只返回f1和f2:

 typedef struct Foo { int a; struct Foo* foo; } Foo; static Foo* link_f1(); static Foo* link_f2(); static Foo f1 = {1, link_f2()}; static Foo f2 = {2, link_f1()}; static Foo* link_f1() { return &f1; } static Foo* link_f2() { return &f2; } 

不幸的是,这不是有效的C,所以你仍然需要不同的C和C ++代码。

我会创建两个文件(.cpp和.h):

code.h:

 typedef struct Foo { Foo() {} Foo(int aa, struct Foo* ff) : a(aa), foo(ff) {} int a; struct Foo* foo; } Foo; static Foo f1; static Foo f2; 

code.cpp:

 void myfun() { f1 = Foo(1, &f2); f2 = Foo(2, &f1); } 

我还希望将所有变量(如f1,f2 …)放入某种“存储”对象(我自己的类或某些STL容器)中。 然后,我将此对象定义为静态对象。

这是我在项目中所做的。 我没有尝试使用匿名命名空间来解决这个问题,而是使用了命名空间。

[然后感谢Matt McNabb的有用评论,事实certificate,一个匿名命名空间将用于一个超级整洁的解决方案,其中较少的宏不会产生外部名称污染。 ]

这允许我有两个独立的程序文本区域,中间有常规文件范围,以获得一个整洁的解决方案:

一切都隐藏在这些宏的背后:

 #ifdef __cplusplus #define static_forward(decl) namespace { extern decl; } #define static_def(def) namespace { def; } #else #define static_forward(decl) static decl; #define static_def(def) static def; #endif 

所以我们可以这样做:

 static_forward(struct foo foo_instance) void some_function(void) { do_something_with(&foo_instance); } static_def(struct foo foo_instance = { 1, 2, 3 }) 

C扩展很简单,看起来像这样:

 static struct foo foo_instance; void some_function(void) { do_something_with(&foo_instance); } static struct foo foo_instance = { 1, 2, 3 }; 

C ++扩展如下所示:

 namespace { extern struct foo foo_instance; } void some_function(void) { do_something_with(&foo_instance); } namespace { struct foo foo_instance = { 1, 2, 3 }; } 

因此,简而言之,由于匿名命名空间,C ++实际上并没有静态前向引用问题,只有以C不兼容的方式实现它的问题,它可以与宏桥接。

同一个转换单元中的多个匿名命名空间区域是相同的命名空间,命名空间外部的周围文件范围可以看到它。