Если у Вас есть список полей, которые привыкают для набора вещей, например, определения структуры, сериализации той структуры к/от некоторому двоичному формату, выполнение базы данных вставляет, и т.д., то Вы можете (рекурсивно!) используют препроцессор, чтобы не когда-либо повторять Ваш cписок полей.
Это по общему признанию отвратительно. Но возможно иногда лучше, чем обновление длинного списка полей в нескольких местах? Я использовал эту технику точно однажды, и было довольно полезно что одно время.
, Конечно, то же общее представление используется экстенсивно на языках с надлежащим отражением - просто instrospect класс, и воздействуйте на каждое поле в свою очередь. Выполнение его в препроцессоре C является хрупким, неразборчивым, и не всегда портативным. Таким образом, я упоминаю его с некоторым трепетом. Тем не менее, здесь это...
(РЕДАКТИРОВАНИЕ: Я вижу теперь, когда это подобно тому, что @Andrew Johnson сказал относительно 9/18; однако идея рекурсивно включая тот же файл берет идею немного далее.)
// file foo.h, defines class Foo and various members on it without ever repeating the
// list of fields.
#if defined( FIELD_LIST )
// here's the actual list of fields in the class. If FIELD_LIST is defined, we're at
// the 3rd level of inclusion and somebody wants to actually use the field list. In order
// to do so, they will have defined the macros STRING and INT before including us.
STRING( fooString )
INT( barInt )
#else // defined( FIELD_LIST )
#if !defined(FOO_H)
#define FOO_H
#define DEFINE_STRUCT
// recursively include this same file to define class Foo
#include "foo.h"
#undef DEFINE_STRUCT
#define DEFINE_CLEAR
// recursively include this same file to define method Foo::clear
#include "foo.h"
#undef DEFINE_CLEAR
// etc ... many more interesting examples like serialization
#else // defined(FOO_H)
// from here on, we know that FOO_H was defined, in other words we're at the second level of
// recursive inclusion, and the file is being used to make some particular
// use of the field list, for example defining the class or a single method of it
#if defined( DEFINE_STRUCT )
#define STRING(a) std::string a;
#define INT(a) long a;
class Foo
{
public:
#define FIELD_LIST
// recursively include the same file (for the third time!) to get fields
// This is going to translate into:
// std::string fooString;
// int barInt;
#include "foo.h"
#endif
void clear();
};
#undef STRING
#undef INT
#endif // defined(DEFINE_STRUCT)
#if defined( DEFINE_ZERO )
#define STRING(a) a = "";
#define INT(a) a = 0;
#define FIELD_LIST
void Foo::clear()
{
// recursively include the same file (for the third time!) to get fields.
// This is going to translate into:
// fooString="";
// barInt=0;
#include "foo.h"
#undef STRING
#undef int
}
#endif // defined( DEFINE_ZERO )
// etc...
#endif // end else clause for defined( FOO_H )
#endif // end else clause for defined( FIELD_LIST )
что делает их лучше, чем написание собственной библиотеки?
При развертывании первой версии вашего приложения, вероятно, ничего: ваши потребности четко определены, и вы разработаете систему обмена сообщениями, которая будет соответствовать вашим потребностям: небольшой список функций, небольшой исходный код и т.п. Позвольте мне привести несколько вариантов использования:
вначале у вас было 3 машины внутри локальной сети, без заметных задержек, все попадает на каждую машину. появляется ваш клиент / босс / заостренный-дьявол-босс и сообщает вам, что вы установите приложение в WAN, которым вы не управляете, - а затем у вас начнутся сбои соединения, плохая задержка и т. д. вам нужно сохранить сообщение и повторить попытку отправки их позже: вернитесь к коду и вставьте все это (и наслаждайтесь)
отправленные сообщения должны иметь ответы, но не все из них: вы отправляете некоторые параметры и ожидаете в результате электронную таблицу вместо того, чтобы просто отправлять и подтверждать, вернитесь к коду и вставьте все это (и наслаждайтесь.)
И многие другие варианты использования, которые я забыл ...
Вы можете реализовать это самостоятельно, но не тратьте на это много времени: вы, вероятно, все равно замените его позже.
)И многие другие варианты использования, которые я забыл ...
Вы можете реализовать это самостоятельно, но не тратьте на это много времени: вы, вероятно, все равно замените его позже.
)И многие другие варианты использования, которые я забыл ...
Вы можете реализовать это самостоятельно, но не тратьте на это много времени: вы, вероятно, все равно замените его позже.
несмотря на то, что вы фактически инициализируете его интегральным постоянным выражением в другой единице перевода. (* Это не совсем ясно из формулировки стандарта, но это моя текущая интерпретация.)Ограничения в стандарте запрещают использование, например:
void f(int a, int b)
{
const int c = b;
switch (a)
{
case c:
//...
}
}
В вашем примере, когда компилятор компилирует test.cpp
, у него нет способа определить, какой инициализатор может быть в x_def.cpp
. Вы могли бы сделать:
const int test_int = (int)time();
Очевидно, что ни в одном из этих примеров нельзя определить значение const int
во время компиляции, что является целью для интегральных константных выражений.
void f(int a, int b)
{
const int c = b;
switch (a)
{
case c:
//...
}
}
В вашем примере, когда компилятор компилирует test.cpp
, у него нет способа определить, какой инициализатор может быть в x_def.cpp
. Вы могли бы сделать:
const int test_int = (int)time();
Очевидно, что ни в одном из этих примеров нельзя определить значение const int
во время компиляции, что является целью для интегральных константных выражений.
void f(int a, int b)
{
const int c = b;
switch (a)
{
case c:
//...
}
}
В вашем примере, когда компилятор компилирует test.cpp
, у него нет способа определить, какой инициализатор может быть в x_def.cpp
. Вы могли бы сделать:
const int test_int = (int)time();
Очевидно, что ни в одном из этих примеров нельзя определить значение const int
во время компиляции, что является целью для интегральных константных выражений.
Метки case должны быть константами времени компиляции. Это означает, что компилятор должен иметь возможность заменять значение во время компиляции. Хотя ваши значения постоянны, компилятор не может узнать их значения, по крайней мере, до времени компоновки.
Я не могу воспроизвести это на тривиальном примере с помощью VC ++ 2008:
test.cpp:
extern const int n;
int main() {
switch (0) {
case n: break;
}
}
test2.cpp:
extern const int n = 123;
скомпилировать с:
cl.exe test.cpp test2.cpp
вывод:
test.cpp (4): ошибка C2051: выражение case не константа
VC ++ правильный, а g ++ неправильный. Метка case должна быть интегральным константным выражением
(§6.4.2 / 2), а константная переменная целочисленного типа, инициализированная константным выражением, является константным выражением (§5.19 / 1).
Edit: в основном для Павла и его предложения о возможном DR. §5.19 / 2 был полностью переписан. C ++ 0x добавляет совершенно новую концепцию constexpr
, которая значительно расширяет то, что считается константным выражением. Например, в соответствии с текущим стандартом что-то вроде:
int x() { return 10; }
const int y = x();
y
не является постоянным выражением. Мы все легко можем видеть, что он (косвенно) инициализируется литералом 10
, но компилятор по-прежнему не может разрешить его как постоянное выражение. По новому стандарту это ' Можно будет обозначить x ()
как constexpr
, а y
будет постоянным выражением.
Как это сформулировано в N2960, §5.19 / 2 говорит, что выражение является постоянным выражением, если оно не использует что-либо из следующего списка. Затем он дает список длиной в страницу, но использование переменной const
, которая не инициализирована в текущем модуле компиляции, не кажется одним из них. [Изменить: см. Ниже - прочитав выпуск 721 CWG, я передумал.]
Что касается VC ++, то есть правильности, а g ++ - неправильности, я имел в виду только этот очень конкретный аспект. Нет никаких сомнений в том, что оба «неправильны», если вы говорите о правильности каждой части стандарта. Я сомневаюсь, что кто-то вообще работает над реализацией экспорта
для любого из них.
export
, однако, указывает на степень, в которой C ++, кажется, готов откладывать решения до времени компоновки. Двухэтапный поиск имени означает, что при компиляции экспортированного шаблона есть гораздо больше, чем просто постоянные выражения, о которых он точно не знает. Он может даже не знать, относится ли конкретное имя к функции или объекту, но нет никаких сомнений в том, что стандарт требует именно этого. Эта проблема кажется мне существенно более простой для решения.
Редактировать: Я немного поискал и обнаружил проблему 721 основной рабочей группы. Вопрос Джейма довольно близко соответствует рассматриваемому («Однако для этого не требуется, как предположительно должно, чтобы инициализация происходила в той же единице перевода и предшествовала константному выражению ...»). Предлагаемое постановление добавляет фразу: «... с предшествующей инициализацией ...». По крайней мере, когда я ее читал, это означает, что комитет согласился с тем, что в соответствии с текущим стандартом код должен быть принят, но в соответствии с новым стандартом это не разрешено.
Эта формулировка была согласована в июле этого года, но не (пока?) не появился в N2960, который, я считаю, является самым последним проектом C ++ 0x.
Компилятор MS здесь немного шалит. Когда вы компилируете инициализацию константы и оператор case с использованием константы в одном модуле компиляции, она вычисляет значение константы во время компиляции.
Как только вы пытаетесь использовать extern const
вне модуля компиляции, в котором он инициализирован (т. е. файл cpp, содержащий инициализацию, или любой из файлов, которые он включает), компилятор отключит примерно такую же ошибку. Фред Ларсон прав, компилятор не должен знать постоянное значение до времени компоновки, и, следовательно, оно не должно быть приемлемым в качестве константы переключения, это просто компилятор MS немного обманывает.
Решение вашего проблема заключается в использовании макросов, есть ли причина, по которой вы не
Вот более простой тест:
test_int.cpp:
const int test_int = 10;
main.cpp:
#include <iostream>
using std::cout;
using std::endl;
extern const int test_int;
int main() {
cout << test_int << endl;
return 0;
}
В G ++ я получаю неопределенную ссылку. Однако то же самое в C работает. Согласно http://gcc.gnu.org/ml/gcc/2005-06/msg00325.html , переменная const неявно имеет внутреннюю связь в C ++. Похоже, что это не так в C.
Я использую "gcc (SUSE Linux) 4.3.2" и имею похожий эффект, но это все еще немного странно.
Мои определения:
namespace operations{
const cOpDummy OpDummy();
const cInitOperator InitOperator();
};
const unsigned long ulNumberOfOperations = 2;
const cOperation * arrayOperations[] = {
& (operations::OpDummy),
& (operations::InitOperator)
};
А объявления extern в другом файле:
extern const unsigned long ulNumberOfOperations;
extern const cOperation * arrayOperations[];
Забавная вещь: Компилятор выдает только для "ulNumberOfOperations" "undefined reference to ulNumberOfOperations", но с "arrayOperations[]" все в порядке. Мой обходной путь - объявить "ulNumberOfOperations" не константой.