【问题标题】:Forward declaring static C struct instances in C++在 C++ 中转发声明静态 C 结构实例
【发布时间】:2009-02-28 17:43:16
【问题描述】:

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

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 编译器来编译某些文件?

【问题讨论】:

  • 也许我只是太密集了,但你想达到什么目的?你没有向前声明任何东西,你正在创建 Foo 类型的变量,多次使用相同的名称。但我不确定为什么。为什么不只声明每个变量一次?
  • 你不能声明一次变量,因为它们是交叉引用的:static Foo f1 = {1, &f2};静态 Foo f2 = {2, &f1}; - 你必须转发声明。

标签: c++ c


【解决方案1】:

这应该使用 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

【讨论】:

  • 我们赢了!太棒了——这正是我想要的。谢谢。
  • 我对 C++ 的信心已经恢复。
  • 这也有效:static Foo& f1 = StaticFoos::f1;静态 Foo& f2 = StaticFoos::f2;并且不用指针也能达到同样的效果。
  • 或:静态 const Foo& f1 = StaticFoos::f1; static const Foo& f2 = StaticFoos::f2;
  • 已更新以反映您的建议。
【解决方案2】:

这似乎与 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 的外部定义在目标文件中以可外部链接的符号公开;但是,因为它们位于匿名命名空间内,所以符号会以不会与来自另一个翻译单元的符号冲突的方式进行修改。

使用宏魔法,您可以进行设置,因此只有一个地方可以声明和定义 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

【讨论】:

  • 是的,哈哈,我的想法是一样的。你得到了我的投票,竖起大拇指:)
  • 另外,外部的东西避免使用不推荐使用的东西(静态在 C++ 中被弃用)并允许它们作为模板参数传递(不能将静态变量作为参数传递给模板。只有那些具有外部链接的)。但他们仍然在当地被有效地破坏了。整洁的! :)
【解决方案3】:

您试图避免的称为Static Initialization Order Fiasco。您可以使用函数代替,也可以使用默认值初始化各个对象,然后重新设置成员指针。

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

extern Foo f1;

这是一个声明和暂定定义。

static Foo f1;

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

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 */

这很好,因为可以有多个临时声明。

【讨论】:

  • 是的,在原帖中已经说过了。问题是如何在 C++ 中使用静态结构实例来达到与 C 相同的效果。在 C++ 中似乎不可能。这似乎是语言疏忽。
  • @dirkgently:它是有效的 C,因为 C 标准说:“在一个翻译单元中,每个具有内部链接的标识符声明都表示相同的对象或函数”。
  • 令人惊讶的是,这种 C 功能无法在 C++ 中实现。 C++ 不是设计成 C 的超集吗?
  • @Michael Burr:我相信这是为了外部链接。
  • @Michael Burr:找不到合适的部分,所以我和你一起去。我的错。再次感谢。
【解决方案4】:

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

编辑: Michael Burr 在评论中提到了原因,我想我会将其添加到帖子中:

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

C++ 没有这样的规则。

编辑:

如另一篇文章所述。您也可以使用匿名命名空间来限制变量的范围。只需将命名空间的内容包装在 #ifdef __cplusplus 中,就可以开始使用了。

【讨论】:

  • 很遗憾,C++ 缺乏转发声明结构实例的能力,就像您在 C 中所做的那样。extern 污染了全局变量空间,这是我试图避免的。
【解决方案5】:

我遇到过这个问题。这种限制令人沮丧,我看不出 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++ 的不同代码。

【讨论】:

  • C 的特性称为暂定定义,而不是前向声明。 static Foo f1; 实际上定义了 f1,但是 C 允许您稍后指定一个初始化程序,只要只有一个初始化程序定义。这也可以用数组来完成。
【解决方案6】:

我会创建两个文件(.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 容器)。然后,我会将这个对象定义为静态对象。

【讨论】:

    【解决方案7】:

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

    [ 然后感谢 Matt McNabb 的帮助 cmets,事实证明,匿名命名空间将成为一个超级整洁的解决方案,它使用更少的宏,不会产生外部名称污染。 ]

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

    一切都隐藏在这些宏后面:

    #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 不兼容的方式实现它的问题,这可以与宏桥接。

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

    【讨论】:

    • 你可以使用匿名命名空间namespace {
    • @MattNcNabb 我似乎相信,也许是错误的,匿名命名空间的每个实例都像一个具有唯一秘密名称的新命名空间。这个技巧依赖于两个不连续的命名空间区域是同一个命名空间,因为一个声明一个名称,另一个定义它。此外,外部区域指的是这些标识符。有没有办法从外部引用 namespace { 中的名称?
    • 哦,我误解了你在做什么。您在“C++ 扩展”中的代码通过具有多个foo_instance 定义导致未定义的行为。我的编译器 (g++ 4.9.2) 甚至拒绝了代码。
    • 同一个TU中的所有匿名命名空间都引用同一个命名空间,所以如果你把代码改成namespace { extern foo foo_instance; } ... namespace { foo foo_instance = {1,2,3}}那么代码是正确的,它不会污染全局命名空间.
    • @MattMcNabb 唉,即使命名空间引用同一个,我们如何从外部引用命名空间?啊,我明白了,有一个隐含的“使用命名空间 ”。
    猜你喜欢
    • 1970-01-01
    • 2013-10-08
    • 2021-03-10
    • 1970-01-01
    • 2013-10-14
    • 1970-01-01
    • 2019-02-04
    • 1970-01-01
    • 2021-12-13
    相关资源
    最近更新 更多