我正在编写一个代码生成器,实际上是一个数据生成器,它将生成这种形式的数据结构(显然实际的数据结构更精细):
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 ++编译器都是可移植的。
我想将这些结构实例声明为静态,以免污染全局变量空间,如:
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编译器来编译某些文件?
答案 0 :(得分:7)
这应该使用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 ++中,您可以访问f1
和f2
。
答案 1 :(得分:7)
这似乎与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
答案 2 :(得分:4)
您要避免的内容称为Static Initialization Order Fiasco。您将很好地使用函数,并使用默认值初始化单个对象,然后重置成员指针。
您的代码示例意味着完全不同的东西。你将不得不重新审视。第一个成功,因为你有一个对象的定义和另一个对象的声明。这适用于C和C ++。
extern Foo f1;
这是一个宣言和一个暂定的定义。
static Foo f1;
这是f1
类型的对象struct Foo
的声明和定义。
static Foo f2;
同上。
static Foo f1 = {1, &f2};
这是重新定义。您违反了One Definition Rule,其中说明翻译单元中必须有一个且只有一个符号定义。除此之外,您可以拥有多个定义,但当然每个事件都必须具有相同的语法和语义。
static Foo f2 = {2, &f1};
同上。
extern Foo fn;
/* some code */
extern Foo fn;
/* some more ... */
Foo fn; /* finally a definition */
这很好,因为可以有多个暂定声明。
答案 3 :(得分:2)
您无法转发声明对象,只能转发类型。外部解决方案是正确的解决方案。或者,如果您确实需要避免全局命名空间污染,请将它们设置为静态并使用您在所有其他命令之前调用的函数对它们进行初始化。
编辑: Michael Burr在评论中提到了原因,我想我会把它添加到帖子中:
@dirkgently:它是有效的C因为 C标准说:“在一个 翻译单位,每个声明 具有内部链接的标识符 表示相同的对象或功能“。
C ++没有这样的规则。
修改强>
如另一篇文章所述。您也可以使用匿名命名空间来限制变量的范围。只需将命名空间内容包装在#ifdef __cplusplus
中,您就应该好了。
答案 4 :(得分:1)
我遇到过这个问题。限制是令人沮丧的,我没有看到任何理由为什么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 ++代码。
答案 5 :(得分:0)
我会创建两个文件(.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容器)中。然后,我将此对象定义为静态对象。
答案 6 :(得分:0)
这是我在项目中所做的。我没有尝试使用匿名命名空间来解决这个问题,而是使用了命名空间。
[然后感谢Matt McNabb的有用评论,事实证明,一个匿名命名空间将用于一个超级整洁的解决方案,其中较少的宏不会产生外部名称污染。 ]
这允许我有两个独立的程序文本区域,中间有常规文件范围,以获得整洁的解决方案:
这些宏背后隐藏着一切:
#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不兼容的方式实现它的问题,可以与宏桥接。
同一个翻译单元中的多个匿名命名空间区域是相同的命名空间,命名空间外的周围文件范围可以看到它。