При использовании g ++ 3.4.6 (с ld 2.15.92.0.2) я могу написать:
class ConstantContainer {
public:
static const uint16_t MyConstant1 = UINT16_C(0x4321);
static const uint32_t MyConstant2 = UINT32_C(0x87654321);
static const uint64_t MyConstant3 = UINT64_C(0xF0E1D2C3B4A59687);
static const double MyConstant4 = 1.0 / 4.0;
};
и использовать ConstantContainer::MyConstant1
и другие почти повсеместно в качестве дешевого заменителя литералов области действия, за исключением инициализации других констант.
Однако при использовании g ++ 3.3.6 (с ld той же версии 2.15.92.0.2, но не с тем же двоичным файлом и дистрибутивом), код тоже хорошо компилируется, но связывание не удается в некоторых случаях из-за неразрешенной ссылки в любой точке используется «константа»:
g++ -o myapp module1.o module2.o ... main.o
moduleN.o(.text+0x59e): In function `BlahBlah(const FooBar&)':
: undefined reference to `ConstantContainer::MyConstant1'
Я не мог понять, какие уникальные особенности провоцируют такое поведение. Например, несовместимый случай может быть таким простым:
class GraphConversionState {
public:
struct NodeIndex {
public:
typedef CxxStd::uint32_t ValueType;
ValueType Value;
class ValueSpecial {
public:
static CxxConstExpr ValueType
Unknown = UINT32_C(0xFF000000),
Isolated = UINT32_C(0xFF111111),
Connected = UINT32_C(0xFFCCCCCC);
};
};
};
И. е. есть только небольшая группа статических константных членов типа uint, но они не могут рассматриваться как именованные литералы; Между тем, в других случаях даже значения с плавающей запятой хороши. Единственное очевидное различие — это уровень области (вложенность классов), но это не является реальной причиной в общем случае с упрощенными примерами.
Очевидный обходной путь — превратить вышеупомянутый класс в монстра:
class ConstantContainerType {
public:
uint16_t MyConstant1;
uint32_t MyConstant2;
uint64_t MyConstant3;
double MyConstant4;
ConstantContainerType() :
MyConstant1(UINT16_C(0x4321)),
MyConstant2(UINT32_C(0x87654321))
MyConstant3(UINT64_C(0xF0E1D2C3B4A59687))
MyConstant4(1.0 / 4.0)
{ }
};
static const ConstantContainerType ConstantContainer;
// in ConstantContainer.cpp:
const ConstantContainerType ConstantContainer;
Но это довольно уродливо, менее чисто и намного более подвержено ошибкам, так как число констант и контейнерных классов велико. Более того, хотя объявленные и определенные константы на месте, вероятно, оптимизированы, поскольку они были настоящими литералами, весьма сомнительно, что они будут рассматриваться так, когда они являются частью объекта-одиночки.
Поэтому я задался вопросом: каковы точные правила, используемые GCC 3.3 и выше для обработки некоторых статических объявлений const POD как константных определений?
Либо вы можете определить это в другом месте,
class ConstantContainer {
public:
static const uint16_t MyConstant1;
static const uint32_t MyConstant2;
};
ConstantContainer::MyConstant1 = UINT16_C(0x4321);
ConstantContainer::MyConstant2 = UINT32_C(0x87654321);
или сделайте второй уборщик, объявив элементы константами.
class ConstantContainer {
public:
const uint16_t MyConstant1;
const uint32_t MyConstant2;
ConstantContainer(uint16_t foo, uint16_t bar)
:MyConstant1(foo), MyConstant2(bar)
{}
};
Старый трюк все еще работает:
class ConstantContainer {
public:
enum { MyConstant1 = UINT16_C(0x4321) };
enum { MyConstant2 = UINT32_C(0x87654321) };
};
Конечно, если вы хотите реальные объекты типа uint16_t/uint32_t
им придется где-то жить.
Просто используйте пространство имен вместо злоупотребления такими классами:
namespace ConstantContainer {
uint16_t const MyConstant1 = UINT16_C(0x4321);
uint32_t const MyConstant2 = UINT32_C(0x87654321);
}
Кроме того, константы в области пространства имен по умолчанию имеют внутреннюю связь (например, статические объекты на уровне пространства имен), поэтому вы можете объявлять и определять их в заголовочном файле без риска нарушения ODR.