我使用constexpr在编译时计算哈希码。代码编译正确,运行正常。但我不知道,如果哈希值是编译时间或运行时间。如果我在运行时跟踪代码,我不会进入constexpr函数。但是,即使对于运行时值也不会跟踪它们(计算运行时生成的字符串的哈希值 - 相同的方法)。 我试图调查反汇编,但我完全不明白
出于调试目的,我的哈希码只是字符串长度,使用:
constexpr inline size_t StringLengthCExpr(const char * const str) noexcept
{
return (*str == 0) ? 0 : StringLengthCExpr(str + 1) + 1;
};
我有像这样创建的ID类
class StringID
{
public:
constexpr StringID(const char * key);
private:
const unsigned int hashID;
}
constexpr inline StringID::StringID(const char * key)
: hashID(StringLengthCExpr(key))
{
}
如果我在程序main
方法
StringID id("hello world");
我得到了这个反汇编的代码(其中一部分 - 内联方法和其他内容中的内容很多)
;;; StringID id("hello world");
lea eax, DWORD PTR [-76+ebp]
lea edx, DWORD PTR [id.14876.0]
mov edi, eax
mov esi, edx
mov ecx, 4
mov eax, ecx
shr ecx, 2
rep movsd
mov ecx, eax
and ecx, 3
rep movsb
// another code
我怎么能从中得知,“哈希值”是编译时间。我没有看到任何像 11 移动注册的常量。我对ASM不是很好,所以也许它是正确的,但我不确定要检查什么或如何确定,“哈希代码”值是编译时间而不是在运行时从此代码计算。
(我使用的是Visual Studio 2013 +英特尔C ++ 15编译器 - VS编译器不支持constexpr)
修改
如果我更改了我的代码并执行此操作
const int ix = StringLengthCExpr("hello world");
mov DWORD PTR [-24+ebp], 11 ;55.15
我得到了正确的结果
即使有了这个
将私有hashID更改为public
StringID id("hello world");
// mov DWORD PTR [-24+ebp], 11 ;55.15
printf("%i", id.hashID);
// some other ASM code
但是如果我使用私有hashID并添加Getter
inline uint32 GetHashID() const { return this->hashID; };
到ID类,然后我得到了
StringID id("hello world");
//see original "wrong" ASM code
printf("%i", id.GetHashID());
// some other ASM code
答案 0 :(得分:22)
最方便的方法是在constexpr
声明中使用static_assert
。在编译期间未编译代码时,代码将无法编译,static_assert
表达式将在运行时不会产生任何开销(并且没有像模板解决方案那样的不必要的生成代码)。
示例:
static_assert(_StringLength("meow") == 4, "The length should be 4!");
这也会检查您的功能是否正确计算结果。
答案 1 :(得分:5)
如果要确保在编译时评估constexpr
函数,请将其结果用于需要编译时评估的内容:
template <size_t N>
struct ForceCompileTimeEvaluation { static constexpr size_t value = N; };
constexpr inline StringID::StringID(const char * key)
: hashID(ForceCompileTimeEvaluation<StringLength(key)>::value)
{}
请注意,我已将该功能重命名为StringLength
。以下划线后跟大写字母或包含两个连续下划线的名称在用户代码中不合法。它们保留用于实现(编译器和标准库)。
答案 2 :(得分:1)
有几种方法可以强制进行编译时评估。但这些并不像您在使用constexpr
时所期望的那样灵活且易于设置。并且它们无法帮助您查找是否实际使用了编译时常量。
constexpr
您想要的是在您期望它有益的地方工作。因此,您尝试满足其要求。但是,您需要测试是否已生成您希望在编译时生成的代码,以及用户是否实际使用生成的结果或在运行时触发该函数。
我找到了两种方法来检测类或(成员)函数是否正在使用编译时或运行时评估路径。
constexpr
函数的属性从noexcept运算符(true
)返回bool noexcept( expression )
。由于生成的结果将是编译时常量。这种方法非常易于使用,可用于单元测试
(请注意,明确标记这些函数noexcept
将会破坏测试。)Source: cppreference.com (2017/3/3)
因为noexcept运算符总是为常量表达式返回true,所以它可用于检查constexpr函数的特定调用是否采用常量表达式分支(...)
constexpr
的函数内放置一个断点。每当触发断点时,都会使用编译器评估结果。不是最容易的,但偶然检查可能。Soure: Microsoft documentation (2017/3/3)
注意:在Visual Studio调试器中,您可以通过在其中放置断点来判断是否在编译时评估constexpr函数。如果命中断点,则在运行时调用该函数。如果没有,则在编译时调用该函数。
我在尝试使用constexpr
时发现这两种方法都很有用。虽然我还没有对VS2017以外的环境进行任何测试。并且还没有能够在当前的标准草案中找到支持这种行为的明确声明。
答案 3 :(得分:1)
以下技巧可以帮助检查constexpr函数是否仅在编译期间进行了评估:
使用gcc,您可以使用汇编列表+ c源编译源文件;鉴于constexpr及其调用都在源文件try.cpp
中 gcc -std=c++11 -O2 -Wa,-a,-ad try.cpp | c++filt >try.lst
如果在运行时间期间评估了constexpr函数,那么您将在汇编列表try.lst中看到已编译的函数和调用指令(在x86上调用function_name)(请注意,c ++ filt命令未修饰链接器名称)
有趣的是,如果没有优化编译(没有-O2或-O3选项),我总是看到一个调用。
答案 4 :(得分:0)
只需将其放在constexpr变量中即可。
constexpr StringID id("hello world");
constexpr int ix = StringLengthCExpr("hello world");
constexpr变量始终是实常数表达式。如果编译,则在编译时进行计算。
答案 5 :(得分:0)
在将来的(c ++ 20)中,可以使用consteval说明符来声明一个函数,该函数必须在编译时进行评估,因此需要一个constant expression上下文。
consteval说明符将一个函数或函数模板声明为 直接函数,即对函数的每次调用都必须 (直接或间接)产生一个编译时间常数表达式。
来自cppreference的示例(请参见consteval):
consteval int sqr(int n) {
return n*n;
}
constexpr int r = sqr(100); // OK
int x = 100;
int r2 = sqr(x); // Error: Call does not produce a constant
consteval int sqrsqr(int n) {
return sqr(sqr(n)); // Not a constant expression at this point, but OK
}
constexpr int dblsqr(int n) {
return 2*sqr(n); // Error: Enclosing function is not consteval and sqr(n) is not a constant
}