Прямое объявление экземпляров статической структуры C в C++

Я пишу генератор кода, ну, фактически генератор данных, который будет создавать структуры данных этой формы (очевидно, что фактические структуры данных намного сложнее):

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 для компиляции определенных файлов?

Ответов (7)

То, чего вы пытаетесь избежать, называется Fiasco статического порядка инициализации . Вам будет удобно использовать вместо этого функции, а также инициализировать отдельные объекты значениями по умолчанию, а затем переустановить указатели на элементы.

Ваши образцы кода означают совершенно разные вещи. Придется пересмотреть. Первый успешен, потому что у вас есть определение одного объекта и объявление другого. Это будет работать как с C, так и с C++.

extern Foo f1;

Это декларация и предварительное определение.

static Foo f1;

Это объявление и определение объекта f1 типа struct Foo .

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

Это нормально, так как можно иметь несколько предварительных заявлений.

Я столкнулся с этой проблемой. Ограничение расстраивает, и я не вижу причин, по которым 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++.

Вот что я сделал в своем проекте. Вместо того, чтобы пытаться решить эту проблему с помощью анонимного пространства имен, я использовал именованное пространство имен.

[И затем, благодаря полезным комментариям Мэтта Макнабба, выяснилось, что анонимное пространство имен подойдет для очень аккуратного решения с меньшим количеством макросов, которое не создает внешнего загрязнения имен. ]

Это позволяет мне иметь две отдельные области текста программы с обычной областью файлов между ними для аккуратного решения:

За этими макросами скрыто все:

#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 способом, который можно связать с помощью макросов.

Несколько областей анонимного пространства имен в одной и той же единице перевода являются одним и тем же пространством имен, и окружающая область файлов за пределами пространства имен имеет видимость в нем.

Вы не можете пересылать объявленные объекты, только типы. Внешнее решение - правильное решение. Или, если вам действительно нужно избежать загрязнения глобального пространства имен, сделайте их статическими и инициализируйте их с помощью функции, которую вы вызываете раньше всех остальных.

РЕДАКТИРОВАТЬ: Майкл Берр упомянул причину в комментарии, я подумал, что добавлю ее в сообщение:

@dirkgently: это допустимо для C, потому что стандарт C гласит: «В пределах одной единицы перевода каждое объявление идентификатора с внутренней связью обозначает один и тот же объект или функцию».

В C++ такого правила нет.

РЕДАКТИРОВАТЬ:

Как отмечено в еще одном сообщении. Вы также можете использовать анонимное пространство имен, чтобы ограничить область действия переменной. Просто оберните материал пространства имен в файл, #ifdef __cplusplus и все будет хорошо.

Я бы создал два файла (.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). Затем я бы определил этот объект как статический.

Он должен компилироваться либо с 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 .

Похоже, это имеет эффект, аналогичный ответу Джоша, но с меньшей сложностью:

#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++ определения extern для 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