我已经基于this SO answer实现了constexpr map 数组查找,但是这让我想知道如果 map 会产生什么样的内存开销?数组非常大,使用此技术可能还会存在其他陷阱,尤其是在编译时无法解析constexpr函数的情况下。
这是一个人为的代码示例,希望可以使我的问题更加清楚:
enum class MyEnum
{
X0,
X1,
X2,
X3,
X4,
X5
};
struct MyStruct
{
const MyEnum type;
const char* id;
const char* name;
const int size;
};
namespace
{
constexpr MyStruct myMap[] = {
{MyEnum::X0,"X0","Test 0", 0},
{MyEnum::X1,"X1","Test 1", 1},
{MyEnum::X2,"X2","Test 2", 2},
{MyEnum::X3,"X3","Test 3", 3},
{MyEnum::X4,"X4","Test 4", 4},
{MyEnum::X5,"X5","Test 5", 5},
};
constexpr auto mapSize = sizeof myMap/sizeof myMap[0];
}
class invalid_map_exception : public std::exception {};
// Retrieves a struct based on the associated enum
inline constexpr MyStruct getStruct(MyEnum key, int range = mapSize) {
return (range == 0) ? (throw invalid_map_exception()):
(myMap[range - 1].type == key) ? myMap[range - 1]:
getStruct(key, range - 1);
};
#include <iostream>
#include <vector>
#include "example.h"
int main()
{
std::vector<MyEnum> enumList = {MyEnum::X0, MyEnum::X1, MyEnum::X2, MyEnum::X3, MyEnum::X4, MyEnum::X5};
int idx;
std::cout << "Enter a number between 0 and 5:" << std::endl;
std::cin >> idx;
MyStruct test = getStruct(enumList[idx]);
std::cout << "choice name: " << test.name << std::endl;
return 0;
}
Enter a number between 0 and 5:
1
choice name: Test 1
与g++
和-std=c++14
一起编译。
在上面的示例中,尽管getStruct
是constexpr函数,但是直到运行时它才能完全解析,因为idx
的值直到那时才知道。使用优化标志进行编译时,这是否可能会改变内存开销,还是将myMap
的全部内容包含在二进制文件中?是否取决于所使用的编译器和优化设置?
此外,如果头文件包含在多个翻译单元中怎么办? myMap
会重复吗?
我想如果 map 数组变得巨大和/或代码将在更多资源受限的环境(例如嵌入式设备)中使用,这可能很重要。
这种方法还有其他潜在的陷阱吗?
答案 0 :(得分:2)
如果使用非常量表达式调用constexpr函数,它将在运行时调用该函数。
如果使用常量表达式调用getStruct
,则编译器可以仅在编译时调用该函数。然后,getStruct
函数将在运行时“不使用”,编译器可能会对其进行优化。此时,myMap
也将不被使用并进行优化。
就运行时大小而言,它实际上可能小于std::unordered_map
或std::map
;它实际上存储了必要的最少信息。但是它的查找时间会慢很多,因为它必须在O(N)时间中单独比较所有元素,因此它实际上并没有执行映射所要做的事情(减少了查找时间)。
如果您想使其更优化,我将确保仅在恒定表达的情况下使用它:
template<MyEnum key>
struct getStruct
{
static constexpr const MyStruct value = _getStruct(key);
}
Here's some compiler output that shows that the map is optimised out entirely
关于将其包含在多个翻译单元中,由于您使用匿名命名空间对其进行定义,因此它将在每个翻译单元中重复。如果在所有这些代码中都对其进行了优化,则不会有开销,但是对于在运行时查找中使用的每个翻译单元,它仍然会重复。