Перечисление C++11 с членами класса и оптимизация времени соединения constexpr
В моем проекте у меня много перечислений, которые должны иметь дополнительные атрибуты, связанные с членами перечисления, и вспомогательные статические методы, связанные с типом перечисления.
Насколько я знаю, это невозможно сделать со стандартным классом перечисления 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 (вместо использования ссылки на один глобальный объект). статический экземпляр), и я боюсь, что это может привести к снижению производительности (мои реальные перечисления имеют больше атрибутов, чем просто один элемент, поэтому локальная реализация может занять некоторое время?). Это оправданное беспокойство?
2 ответа
Я столкнулся с вашим вопросом, пытаясь найти что-то еще (используя современные атрибуты С++ для членов перечисления). Я понимаю, что прошло почти десять лет с тех пор, как вы задали свой вопрос, но, поскольку я здесь, могу также попытаться покинуть этот район лучше, чем я его нашел.
Итак, используя VS2017 и С++14 (я понимаю, что исходный вопрос был для С++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");
- Мне пришлось добавить конструктор по умолчанию, так как я не знаю, как вы обрабатывали ошибки.
- Мне пришлось изменить статические Item1 и Item2, чтобы они были constexpr и были методами, поскольку статические члены требуют инициализатора в классе, но компилятор не распознал MyItemEnum, жалуясь
use of undefined type 'MyItemEnum'
когда он пытался скомпилироватьstatic constexpr MyItemEnum Item1 { MyItem::Item1, 1337 };
. Возможно, это не так в С++ 17 или более поздних версиях. - Просто примечание: constexpr подразумевает встроенный
- Когда вы используете constexpr, определение уже должно быть в области действия, поэтому ни одно из MyItemEnum не может быть определено в .cpp или определено после callsite, если вы хотите использовать их как constexpr.
- С этими изменениями код компилируется, и статические утверждения не дают сбоев.
- Даже с constexpr компилятор может решить не оценивать его во время компиляции. Я рекомендую, когда вы беспокоитесь о codegen, проверять сборку до и после. Есть способы заставить компилятор делать что-то во время компиляции, если вы обнаружите, что он не сотрудничает.
- Для случаев LTCG вам, возможно, придется проверить сборку в окончательном исполняемом файле, поскольку, по крайней мере, в VC++, .obj содержит недокументированные данные, специфичные для компилятора, и не сворачивается в собственный код до момента компоновки (как следует из названия). ).
У меня нет прямого опыта с использованием 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 выполняет ту работу, которая вам нужна.