Прямое объявление экземпляров статической структуры 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)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