GCC 3.3 具有类似于 GCC 3.4 的静态常量变量

Having static const variables with GCC 3.3 similar to GCC 3.4

本文关键字:GCC 静态 变量 常量 类似于      更新时间:2023-10-16

当使用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 和其他作为作用域文本的 cheep 替代品,除了初始化其他常量。

但是,当使用 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 的风险。