C++11枚举类成员和constexpr链接时优化

5
在我的项目中,我有很多枚举需要与枚举成员相关联的附加属性和与枚举类型相关联的辅助静态方法。
据我所知,标准的枚举类MyItem {...}无法实现这一点,因此对于我的每个枚举类,我都有一个辅助类MyItemEnum来封装这些辅助静态方法并实例化自身的辅助实例,以便我可以访问它们的方法以获取其他属性。
下面是一个简化的示例,但我相信所有需要讨论的特性都在其中保留。
MyItem.h
enum class MyItem : unsigned int {
    Item1   = 1,
    Item2   = 5
};

class MyItemEnum {
private:
    MyItem myItem;
    size_t extInfo;

    MyItemEnum(const MyItem& myItem, size_t extInfo);
    ~MyItemEnum();
public:
    static MyItemEnum Item1;
    static MyItemEnum Item2;
    static const MyItemEnum &get(MyItem myItem);

    operator MyItem() const;
    size_t getExt() const;
    bool hasNext() const;
    MyItem next() const;
};

我认为意思很明显,我不需要在此提供.cpp部分...... 我使用MyItem作为参数传递给接口,当我需要访问扩展功能时则使用MyItemEnum。
我的第一个问题是,上述方法是否可行,还是我应该考虑完全不同的方法?
我的第二个问题涉及我正在尝试使用constexpr进行的枚举优化:
enum class MyItem : unsigned int {
    Item1   = 1,
    Item2   = 5
};

class MyItemEnum {
private:
    MyItem myItem;
    size_t extInfo;

    constexpr MyItemEnum(const MyItem& myItem, size_t extInfo);
public:
    static MyItemEnum Item1;
    static MyItemEnum Item2;
    static constexpr MyItemEnum &get(MyItem myItem);

    constexpr operator MyItem();
    constexpr size_t getExt();
    constexpr bool hasNext();
    constexpr MyItem next();
};

它可以编译但显然constexpr没有机会被使用,因为如果我访问:

MyItemEnum::Item1.getExt()

因此编译器不知道Item1实例化时使用了哪些值。 在链接时优化期间,上述表达式是否有可能被评估为constexpr? 或者我可以使用

static constexpr MyItemEnum Item1 = MyItemEnum(MyItem::Item1, 123);

这将激活constexpr编译时优化,但恐怕在某些情况下,当无法进行编译时评估的constexpr时,编译器将不得不创建MyItemEnum的本地实例(而不是使用对单个全局静态实例的引用),我担心这可能会导致性能损失(我的真实枚举类型有多个成员属性,因此本地实例化可能需要一些时间?)。 这是否是一个合理的担忧?

我认为你应该将这个问题分解成不同的问题。 - Yochai Timmer
2个回答

0

我在寻找其他东西(使用现代C++属性对枚举成员进行操作)时遇到了您的问题。我意识到您提出这个问题已经快十年了,但既然我在这里,不妨试着让这个领域变得更好。

因此,使用VS2017和C++14(我知道原始问题是针对C++11的,但现在已经是2022年了,并且我已经按照这种方式设置了一个测试项目),我必须更改代码才能使其编译。

    enum class MyItem : unsigned int {
        Item1 = 1,
        Item2 = 5
    };

    class MyItemEnum {
    private:
        MyItem myItem;
        size_t extInfo;

        constexpr MyItemEnum(MyItem myItemIn, size_t extInfoIn)
            : myItem(myItemIn)
            , extInfo(extInfoIn)
        {}
        constexpr MyItemEnum() : MyItemEnum(MyItem::Item1, ~0U) {}
    public:
        static constexpr MyItemEnum getItem1() { return { MyItem::Item1, 1337 }; }
        static constexpr MyItemEnum getItem2() { return { MyItem::Item2, 0xDEAD }; }
        static constexpr MyItemEnum get(MyItem myItem)
        {
            if (myItem == MyItem::Item1) return getItem1();
            if (myItem == MyItem::Item1) return getItem2();

            // error...
            return MyItemEnum{};
        }

        constexpr operator MyItem() const { return myItem; }
        constexpr size_t getExt() const { return extInfo; }
        constexpr bool hasNext() const { return false; }
        constexpr MyItem next() const
        {
            if (myItem == MyItem::Item1) return getItem2();

            // error...
            return MyItemEnum{};
        }
    };

    static_assert(MyItemEnum::getItem1().getExt()==1337, "Expected 1337");
    static_assert(MyItemEnum::getItem2().getExt()==0xDEAD, "Expected 0xDEAD");

1. 我不知道你如何处理错误,所以我必须添加一个默认构造函数。 2. 我必须将静态成员 Item1 和 Item2 改为 constexpr 并成为方法,因为静态成员需要一个类内初始化器,但编译器在尝试编译 `static constexpr MyItemEnum Item1 { MyItem::Item1, 1337 };` 时无法识别 MyItemEnum,抱怨“使用未定义类型 'MyItemEnum'”。也许在 C++17 或以后的版本中情况不是这样。 3. 只是提醒,constexpr 意味着 inline。 4. 当你使用 constexpr 时,定义必须已经在作用域内,因此如果要将它们用作 constexpr,则不能将 MyItemEnum 中的任何一个定义在 .cpp 中或在调用点后定义。 5. 经过这些更改,代码编译并且静态断言不会失败。 6. 即使使用 constexpr,编译器仍然可能决定 在编译时评估它。当您担心代码生成时,建议您在之前和之后检查汇编。如果发现编译器没有配合,有办法强制编译器在编译时执行操作。 7. 对于 LTCG 的情况,在 VC++ 中至少还需要检查最终可执行文件中的汇编,因为 .obj 包含特定于编译器的未记录数据,并且直到链接时才会折叠为本机代码(顾名思义)。

0

我对使用constexpr和由此产生的编译器优化没有直接经验,但我可以告诉你,在类本身或实例的成员上简单地使用const将使得VS2012和g++ 4.7编译器进行跨模块优化:

class MyItemEnum {
private:
    // make sure to put const here...
    const MyItem myItem;
    const size_t extInfo;

    MyItemEnum(const MyItem& myItem, size_t extInfo);
    ~MyItemEnum();
public:
    // and put const in here too...
    static const MyItemEnum Item1;
    static const MyItemEnum Item2;
};

需要注意的是,构造函数必须使用C++风格的初始化列表语法,如果你只是用常量值填充它们,那么这不应该成为问题。(当需要进行非平凡的设置时,初始化列表才会变得麻烦)。

我没有在Clang/LLVM上验证过这一点,所以如果你使用的是这个工具链,我强烈建议你采用这个简化的例子并自己反汇编结果。即使你不熟悉汇编语言,对于简单的测试用例,反汇编也可以很容易地解析。在这种情况下,你可以编译两个版本:一个在单个模块中,另一个分成两个模块——并比较结果,以确保LTO正在执行你需要的工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接