C++中constexpr 与 explicit关键字使用实战样例
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>概述</li><li>一、constexpr深度讲解</li><ul class="second_class_ul"><li>1. 含义(核心语义)</li><li>2. 演进变化</li><ul class="third_class_ul"><li>新关键词对比(C++20)</li></ul><li>3.constexpr的常见用法示例</li><ul class="third_class_ul"><li>3.1constexpr变量</li><li>3.2constexpr函数(运行期与编译期两用)</li><li>3.3constexpr构造函数(常用于字面类型)</li></ul><li>4.consteval与constinit</li><ul class="third_class_ul"></ul><li>5. 常见误区与陷阱(constexpr)</li><ul class="third_class_ul"></ul><li>6. 实战建议(constexpr)</li><ul class="third_class_ul"></ul></ul><li>二、explicit深度讲解</li><ul class="second_class_ul"><li>1. 含义(核心语义)</li><ul class="third_class_ul"></ul><li>2. 为什么需要explicit</li><ul class="third_class_ul"></ul><li>3.explicit用法示例</li><ul class="third_class_ul"><li>3.1 对构造函数</li><li>3.2 对转换运算符(conversion operator)</li><li>3.3 C++20 条件explicit(expr)</li></ul><li>4. 显式转换运算符的语义细节</li><ul class="third_class_ul"></ul><li>5. 常见误用与陷阱(explicit)</li><ul class="third_class_ul"></ul><li>6. 推荐实践(explicit)</li><ul class="third_class_ul"></ul></ul><li>三、constexpr与explicit的交互要点</li><ul class="second_class_ul"></ul><li>四、实战样例(综合示例)</li><ul class="second_class_ul"></ul><li>五、常见问题快速问答</li><ul class="second_class_ul"></ul><li>六、参考资料</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>概述</h2><ul><li><code>constexpr</code>:声明“该实体可以在编译期求值”,随着 C++ 标准被逐步放宽,现在能在更多场景写编译期函数/构造器/变量。C++20 还新增了 <code>consteval</code>(强制编译期)与 <code>constinit</code>(强制常量初始化)。</li><li><code>explicit</code>:用于阻止不期望的隐式转换。除了对构造函数适用外,C++11 开始可标注转换运算符(conversion operators);C++20 引入了条件 <code>explicit(expr)</code> 能按表达式决定是否为显式。</li></ul>
<p class="maodian"></p><h2>一、constexpr深度讲解</h2>
<p class="maodian"></p><p class="maodian"></p><h3>1. 含义(核心语义)</h3>
<p><code>constexpr</code> 表示:<strong>函数/构造器/变量在满足条件时可以在编译期求值</strong>,从而能用作常量表达式(用于数组维度、模板非类型参数、<code>static_assert</code> 等)。</p>
<ul><li>对象(变量)使用 <code>constexpr</code>:其初始化器必须是常量表达式(直到 C++14 的限制放宽后更灵活)。</li><li>函数/构造函数使用 <code>constexpr</code>:表明在满足参数均为常量表达式的情况下,函数可被用于编译期求值;否则仍可在运行时调用。</li></ul>
<p class="maodian"></p><h3>2. 演进变化</h3>
<p><code>constexpr</code> 的能力随标准放宽:</p>
<ul><li><strong>C++11</strong>:首次引入,函数体必须是单一 <code>return</code>(很受限)。</li><li><strong>C++14</strong>:允许更复杂的函数体(局部变量、循环、分支),<code>constexpr</code> 函数功能大增。</li><li><strong>C++17</strong>:语义继续放宽,数组/结构体初始化等更多场合支持常量求值。</li><li><strong>C++20</strong>:引入 <code>consteval</code>(强制在编译期求值)和 <code>constinit</code>(强制静态变量为常量初始化),并继续扩展 <code>constexpr</code> 支持(例如允许虚函数在某些情形下为 <code>constexpr</code>)。</li></ul>
<p class="maodian"></p><h4>新关键词对比(C++20)</h4>
<ul><li><code>constexpr</code>:可以在编译期或运行期求值(视调用上下文而定)。</li><li><code>consteval</code>:<strong>必须</strong>在编译期求值,若在运行期调用则编译错误。用来写“即时常量函数”。</li><li><code>constinit</code>:用于静态/线程局部变量,<strong>强制</strong>其进行常量初始化(防止静态初始化次序问题)。</li></ul>
<p class="maodian"></p><h3>3.constexpr的常见用法示例</h3>
<p class="maodian"></p><h4>3.1constexpr变量</h4>
<div class="jb51code"><pre class="brush:cpp;">constexpr int square(int x) { return x * x; } // C++14 起允许复杂体
constexpr int five = 5;
constexpr int twentyfive = square(five); // 编译期求值
static_assert(twentyfive == 25);
</pre></div>
<p class="maodian"></p><h4>3.2constexpr函数(运行期与编译期两用)</h4>
<div class="jb51code"><pre class="brush:cpp;">constexpr int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}
int main() {
constexpr int f5 = fib(5); // 编译期
int k; cin >> k;
int r = fib(k); // 运行期也可调用
}
</pre></div>
<p class="maodian"></p><h4>3.3constexpr构造函数(常用于字面类型)</h4>
<div class="jb51code"><pre class="brush:cpp;">struct Point {
double x, y;
constexpr Point(double a, double b) : x(a), y(b) {}
constexpr double norm2() const { return x*x + y*y; }
};
constexpr Point p{3.0, 4.0};
static_assert(p.norm2() == 25.0);</pre></div>
<h5>注意</h5>
<ul><li>直到 C++20,<code>constexpr</code> 成员函数和构造器的语义被逐步增强(允许更多操作,如改变 <code>constexpr</code> 成员变量等)。</li></ul>
<p class="maodian"></p><h3>4.consteval与constinit</h3>
<div class="jb51code"><pre class="brush:cpp;">consteval int must_be_constexpr(int n) { return n*2; }// 必须在编译期调用
constinit int g = must_be_constexpr(10); // g 在编译期初始化
</pre></div>
<p><code>consteval</code> 用于那些<strong>希望强制</strong>在编译期完成的计算(例如生成编译期表或做元编程检查)。</p>
<p class="maodian"></p><h3>5. 常见误区与陷阱(constexpr)</h3>
<ul><li>不是所有 <code>constexpr</code> 函数调用都会在编译期执行:是否在编译期执行取决于调用时传入的参数是否为常量表达式以及使用场景(需要编译期值时才强制求值)。</li><li><code>constexpr</code> 函数内部不能使用非 <code>constexpr</code> 的操作(例如动态分配、IO),否则在尝试编译期求值时会失败(但该函数仍可在运行期被调用,除非用 <code>consteval</code>)。</li><li>不要把 <code>constexpr</code> 当作“强制内联或优化提示” —— 它的本意是可编译期求值;优化与否由编译器决定。</li><li><code>constexpr</code> 与 <code>noexcept</code> 互不替代,但常见 <code>constexpr</code> 函数也声明 <code>noexcept</code>(如果它们无异常)以改善使用场景。</li></ul>
<p class="maodian"></p><h3>6. 实战建议(constexpr)</h3>
<ul><li>对纯计算、常量生成函数使用 <code>constexpr</code>:可在编译期做更多检查并得到更高性能。</li><li>若函数<strong>必须</strong>在编译期求值(设计要求),使用 <code>consteval</code>。</li><li>对类提供 <code>constexpr</code> 构造函数 / 方法以便可在编译期创建字面量对象(例如元数据、表格)。</li><li>在大型项目中谨慎使用 <code>constexpr</code> 以避免过度编译期计算导致编译时间增加。</li></ul>
<p>有关 <code>constexpr</code> 的标准细节参考:<code>cppreference</code> 的 <code>constexpr</code> 条目。</p>
<p class="maodian"></p><h2>二、explicit深度讲解</h2>
<h3>1. 含义(核心语义)</h3>
<p><code>explicit</code> 的目的是 <strong>禁止编译器执行某些隐式转换</strong>,从而避免意外、难以察觉的类型转换错误。它可以修饰:</p>
<ul><li>构造函数(阻止从单参数构造函数的隐式转换) — 自 C++98。</li><li>用户定义的转换运算符(conversion operator)— 自 C++11。</li><li>C++20 引入:<code>explicit(expression)</code>,可以根据编译期表达式条件性地使其显式。</li></ul>
<p class="maodian"></p><h3>2. 为什么需要explicit</h3>
<p>隐式转换在方便的同时会引发<strong>难以发现的逻辑错误</strong>、二义性或意外重载匹配,<code>explicit</code> 能把“自动发生”的转换变成“必须写成 <code>T(x)</code> 或 <code>static_cast<T>(x)</code> 的显式转换”,提高代码可读性与安全性。</p>
<p class="maodian"></p><h3>3.explicit用法示例</h3>
<p class="maodian"></p><h4>3.1 对构造函数</h4>
<div class="jb51code"><pre class="brush:cpp;">struct A {
explicit A(int x) : v(x) {}
int v;
};
void foo(A a) {}
foo(10); // 错误:A(int) 为 explicit,禁止隐式转换
foo(A(10)); // 正确(显式)
foo(static_cast<A>(10)); // 正确</pre></div>
<p class="maodian"></p><h4>3.2 对转换运算符(conversion operator)</h4>
<p>C++11 起可以写 <code>explicit operator T() const</code>,从而禁止隐式转换为 <code>T</code>:</p>
<div class="jb51code"><pre class="brush:cpp;">struct S {
explicit operator bool() const { return true; }
};
S s;
if (s) { }// 不能:implicit conversion to bool is not allowed?
// Actually `if (s)` requires context of boolean; for explicit operator bool, direct-initialization in if condition uses explicit? Explanation below.</pre></div>
<blockquote><p>说明:<code>explicit operator bool()</code> 的引入是为了替代“safe bool idiom”。<code>explicit</code> 转换运算符不会参与某些隐式转换场景,从而避免意外使用。标准对什么时候允许使用显式转换运算符(例如在 <code>if (expr)</code>、<code>static_cast<bool>(expr)</code>、直接初始化等)做了具体规定。详见 <code>cppreference</code>。</p></blockquote>
<p class="maodian"></p><h4>3.3 C++20 条件explicit(expr)</h4>
<p>可以根据模板参数或常量条件使构造函数/转换运算符<strong>有条件地显式</strong>:</p>
<div class="jb51code"><pre class="brush:cpp;">template<typename T>
struct Wrapper {
explicit(sizeof(T) > 4) Wrapper(T); // C++20: 如果 T 大于4字节,则构造器为 explicit
};
</pre></div>
<p>或对转换运算符:</p>
<div class="jb51code"><pre class="brush:cpp;">struct X {
explicit(sizeof(int) <= 4) operator int() const;
};
</pre></div>
<p>这种写法让模板库可以更精细控制隐式转换行为。</p>
<p class="maodian"></p><h3>4. 显式转换运算符的语义细节</h3>
<ul><li>何时可以用显式转换运算符用于上下文? 标准允许在某些上下文需要显式转换的场合使用 <code>explicit</code> 运算符(例如带有直接初始化的场合、显式类型转换 <code>static_cast</code>、以及某些语句/条件表达式),而在需要隐式转换的上下文中(如拷贝初始化)是不被自动使用的。<code>cppreference</code> 对这些规则有详细列出。</li><li><code>explicit operator bool()</code> 与 <code>if (obj)</code>:<code>if</code> 语句要求 “contextually convertible to bool” —— 这允许显式的 <code>operator bool()</code> 被用于条件判断(即 <code>if (obj)</code> 会触发显式转换运算符)——这就是为什么 <code>explicit operator bool()</code> 成为 “safe bool” 的现代替代。更多细节见实践资源。</li></ul>
<p class="maodian"></p><h3>5. 常见误用与陷阱(explicit)</h3>
<ul><li>把每个构造函数都写成 <code>explicit</code> 会减少便利性 —— 设计上应仅对单参数(或可被单参数调用的)构造函数考虑 <code>explicit</code>,以防意外隐式转换。</li><li>对于 conversion operator,<strong>默认不要轻易设为 implicit</strong>(非 <code>explicit</code>),除非非常确定该类型需要自然地作为目标类型使用(例如 <code>std::string::operator std::string_view()</code> 的情况)。一般建议:<strong>prefers named conversion function</strong>(如 <code>.to_string()</code>)而不是提供很多隐式 conversion operators。</li></ul>
<p class="maodian"></p><h3>6. 推荐实践(explicit)</h3>
<ul><li>对单参数构造函数通常使用 <code>explicit</code>,除非希望类型在表达式中自动构造(例如某些数值/枚举封装,需权衡)。</li><li>对用户自定义的转换运算符,<strong>优</strong>先考虑 <code>explicit</code>,只在确实需要隐式转换时才允许隐式。</li><li>使用 C++20 的 <code>explicit(expr)</code> 在模板库中做细粒度控制,而不是写很多重载/模板特化。</li></ul>
<p class="maodian"></p><h2>三、constexpr与explicit的交互要点</h2>
<ul><li>可以写 <code>constexpr explicit</code> 构造函数(非常常见):</li></ul>
<div class="jb51code"><pre class="brush:cpp;">struct S {
constexpr explicit S(int x): v(x) {}
int v;
};
constexpr S s = S(3); // ok: explicit but direct-initialization</pre></div>
<ul><li>也可以写 <code>constexpr explicit operator T()</code>(C++11 起),该转化可在编译期用于常量表达式求值(只要上下文允许)。</li></ul>
<p class="maodian"></p><h2>四、实战样例(综合示例)</h2>
<div class="jb51code"><pre class="brush:cpp;">#include <type_traits>
// C++20 风格:条件 explicit + constexpr + consteval 示范
struct Big {
int x;
constexpr explicit Big(int v) : x(v) {}// constexpr + explicit 构造器
explicit operator int() const { return x; } // explicit conversion operator
};
consteval int must_be_ctime() { return 42; }// 强制编译期求值 (C++20)
constinit int g = must_be_ctime(); // 强制常量初始化 (C++20)
static_assert(std::is_same_v<decltype(static_cast<int>(Big(3))), int>); // OK
// auto a = Big(3); // if function takes Big by value, implicit construction not allowed</pre></div>
<p class="maodian"></p><h2>五、常见问题快速问答</h2>
<p><strong>Q:</strong><code>constexpr</code> 函数内部能用循环/分支吗?<br />A:可以(从 C++14 起放宽),但用于编译期时函数体中的所有操作必须可在编译期执行(不能进行未允许的运行时操作)。</p>
<p>Q:<code>consteval</code> 和 <code>constexpr</code> 的区别?<br />A:<code>consteval</code> 强制编译期求值;<code>constexpr</code> 允许编译期也允许运行期调用。</p>
<p>Q:<code>explicit operator</code> 什么时候应该使用<strong>?</strong><br />A:当你想阻止类型被无意间隐式转换(尤其是布尔/数值上下文)时应使用 <code>explicit</code>;否则可能引发模糊或错误的重载选择。</p>
<p class="maodian"></p><h2>六、参考资料</h2>
<ul><li><code>constexpr specifier (since C++11)</code> — cppreference(详述 <code>constexpr</code> 演进与限制)。</li><li><code>Conversion function (operator type) — cppreference</code>(说明 <code>explicit operator</code> 的语法与语义,及 C++20 条件 explicit)。</li><li><code>const vs constexpr vs consteval vs constinit</code>(总结与对比,含 C++20 新关键字)。</li></ul>
<p>到此这篇关于C++中constexpr 与 explicit关键字使用实战样例的文章就介绍到这了,更多相关C++ constexpr 与 explicit使用内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>C++ 中的 if-constexpr语法和作用</li><li>C++ const与constexpr区别小结</li><li>一文掌握C++ const与constexpr及区别</li><li>C++中关于constexpr函数使用及说明</li><li>C++11 constexpr使用详解</li><li>详解C++中的const和constexpr</li><li>C++中的const和constexpr详解</li><li>C++ 之explicit关键字</li><li>老生常谈C++ explicit关键字</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]