2015-01-29 2 views
1

При использовании г ++ 3.4.6 (с ЛД 2.15.92.0.2), можно написать:Имея статические константные переменные с GCC 3.3 аналогично GCC 3.4

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 и другие почти везде, как заменяют подменю для замеченных литералов, за исключением инициализации других констант.

Однако при использовании г ++ 3.3.6 (с л.д. тех же версий 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); 
     }; 
    }; 
}; 

I. e. существует только небольшая группа статических константных членов типа 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 и выше для обработки некоторых статических константных объявлений POD как постоянных определений?

+3

Очевидный вопрос: нужны ли вам компиляторы gcc-3.x? –

+0

@BrettHale К сожалению, я должен поддерживать такую ​​старую версию - просто потому, что она доступна только на целевой платформе. Я пытаюсь написать будущий код, используя кучу '# define's (a la Boost), но некоторые фундаментальные понятия, подобные этому, нельзя легко эмулировать. –

+2

Я считаю, что 'static const =' отлично в C++ 98, если это * интегральный * тип. Но вам нужна 'const A :: B;' в единицах перевода, на всякий случай кто-то использует: '& A :: B'. Вам может просто повезти с gcc-3.4.x - то есть он выполняет постоянную фальцовку; тогда как gcc-3.3.x пытается получить доступ к постоянному объекту. [ref] (http://www.parashift.com/c++-faq/static-const-with-initializers.html), [подробнее] (http://stackoverflow.com/questions/177437/constststatic) –

ответ

0

Либо вы можете определить его в другом месте,

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) 
    {} 
}; 
+0

Итак, какая разница? Второй пример требует, чтобы статический экземпляр ConstantContainer был создан где-то: нельзя просто получить доступ к константе ConstantContainer :: MyConstant1 в качестве общей константы времени компиляции. –

+0

@ Антон Самсонов. Это зависит от того, хотите ли вы, чтобы один и тот же контейнерный класс содержал одни и те же константы с разными значениями в разных экземплярах. – user3528438

+0

В моем понимании, «константы» - это действительно глобальные инварианты, даже если они технически вычислены во время выполнения для ясности кода и из-за недостатков компилятора. То есть, «константа» - это некоторая ценность, которая никогда не изменится и не будет зависеть от текущей реализации, версии программного обеспечения и других переходных процессов. В противном случае это не «постоянный», а «инвариант времени выполнения». –

0

старый трюк все еще работает:

class ConstantContainer { 
public: 
    enum { MyConstant1 = UINT16_C(0x4321) }; 
    enum { MyConstant2 = UINT32_C(0x87654321) }; 
}; 

Конечно, если вы хотите фактические объекты типа uint16_t/uint32_t, они должны где-то жить.

+0

Этот трюк выглядит очень подверженным ошибкам, поскольку фактический тип данных будет 'int', и как таковой он будет расширяться за пределы ожидаемого размера' uint16', а также обрезать любое значение 'UINT64_C', если только на 64-битной Платформа. –

+1

@ Антон Самсонов: Нет, фактический тип будет (без имени) перечисляемого типа неопределенной, но достаточной ширины. Вы можете быть смущены с C, где тип будет 'int' - за исключением случаев, когда он будет больше. C, как и C++, имеет правило «достаточной ширины». И поскольку C99 заявляет, что должен быть 64-битный 'unsigned long long' (даже на 8-битных платформах!), Компилятор C99 не может обрезать' UINT64_C'. C++ 98 технически не предусматривал 64-разрядные перечисления, но каждый соответствующий игрок имел 64-разрядные типы в любом случае. – MSalters

0

Просто используйте пространство имен вместо злоупотребляя классов так:

namespace ConstantContainer { 
    uint16_t const MyConstant1 = UINT16_C(0x4321); 
    uint32_t const MyConstant2 = UINT32_C(0x87654321); 
} 

Кроме того, константы в области видимости пространства имен имеют внутреннюю связь по умолчанию (например, статических объектов на уровне пространства имен), поэтому вы можете объявить и определить их в файл заголовка без риска нарушений ODR.

+0

Классы - это контейнеры с ограниченным объемом, доступные внутри других классов. Я не знаю о связанных оптимизаторах компилятора, о которых вы упоминаете, но с точки зрения читаемости кода движущиеся константы до уровня пространства имен не лучше, чем использование констант '# define' в глобальной области. –

+0

Hello @AntonSamsonov! Я не упоминаю оптимизацию компилятора. Кроме того, я не вижу, как константы в пространстве имен каким-либо образом похожи на макрос (как макрос может находиться в глобальной области, кстати?). Может быть, вы комментируете неправильный ответ? –

Смежные вопросы