Skip to content

类型推导、值类别与语言规则

constconstexpr

const 表示对象不能通过该名字/访问路径被修改;它的值可以在运行期才确定:

cpp
const int retry_limit = read_config(); // 合法,运行期常量

constexpr 表示变量或函数可以参与常量求值;用来初始化 constexpr 变量的表达式必须在编译期可求值:

cpp
constexpr int square(int x) { return x * x; }
constexpr int buffer_size = square(16); // 256
场景更合适的选择
运行期确定后不再修改的值、只读参数、const 成员函数const
数组界限、非类型模板参数、case 标签、编译期表/数学常量constexpr
希望函数既可编译期计算也可运行期调用constexpr 函数

constexpr 不等于“永远只在编译期运行”:当参数是运行期值时,constexpr 函数仍可在运行期执行。consteval(C++20)才要求每次调用都在编译期求值。

autodecltypedecltype(auto)

auto 按类似模板参数推导的规则从初始化器推导类型,通常会丢弃顶层 const 和引用:

cpp
const int& get_value();

auto a = get_value();           // int:复制值
const auto& b = get_value();    // const int&:显式保留引用

decltype(expr) 获取表达式的精确类型规则:

  • 对未加括号的变量名,得到该变量的声明类型;
  • 对其他左值表达式,得到 T&;对 xvalue 得到 T&&;对 prvalue 得到 T
cpp
int x = 0;
decltype(x) a = x;   // int
decltype((x)) b = x; // int&,因为 (x) 是左值表达式

decltype(auto)decltype 规则推导变量或函数返回类型,适合完美保留返回表达式:

cpp
template <typename T>
decltype(auto) forward_value(T&& value) {
    return std::forward<T>(value);
}

它也更容易意外返回局部变量引用,因此只有需要保留值类别时使用;普通按值返回优先写明确类型或 auto

左值、prvalue 与 xvalue

  • 左值(lvalue):有可识别身份的表达式,如具名变量、返回 T& 的函数调用;
  • 纯右值(prvalue):用于初始化或计算的纯值,如字面量、返回非引用 T 的函数调用;
  • 将亡值(xvalue):仍有身份但资源可被移走的表达式,如 std::move(x)
  • 右值(rvalue):prvalue 与 xvalue 的统称。

std::move(x) 不移动任何资源,只把 x 转换为 xvalue,让重载决议可以选择移动构造/赋值。具名的 T&& 变量本身仍是左值表达式;继续转发时通常需要 std::movestd::forward

其核心可简化为:

cpp
template <typename T>
constexpr std::remove_reference_t<T>&& my_move(T&& value) noexcept {
    return static_cast<std::remove_reference_t<T>&&>(value);
}

T&& 在这里是转发引用:传左值时 T 推导为 U&,传右值时推导为 U;移除引用后统一转换为 U&&。转换本身不会移动数据,真正的资源转移发生在后续调用的移动构造或移动赋值中。

noexcept 与移动操作

不要把“移动构造函数必须标 noexcept”当作绝对规则;应当只在移动确实不会抛异常时标记:

cpp
class Buffer {
public:
    Buffer(Buffer&& other) noexcept
        : data_(std::exchange(other.data_, nullptr)) {}
private:
    int* data_ = nullptr;
};

std::vector 扩容时,若元素可拷贝且移动构造可能抛异常,容器通常倾向拷贝,以保留更强的异常安全保证;若移动为 noexcept,则可安全地移动。对于不可拷贝但移动可能抛异常的类型,容器可能仍需移动,异常保证会变弱。标准不规定容器的增长倍率。

重载决议

重载决议先找候选函数,筛出可行函数,再比较每个参数的隐式转换序列并选择“整体更好”的函数。常见优先级可粗略理解为:

text
精确匹配 / 引用绑定
  优于 类型提升
  优于 标准转换
  优于 用户定义转换
  优于 省略号转换

实际规则还会比较 const 限定、派生到基类、模板与非模板、模板偏序以及初始化列表等,不能机械地当作单一数字排序。若没有唯一最佳可行函数,调用就歧义:

cpp
void f(int, double);
void f(double, int);

f(1, 2); // 两个候选各需要一次同等级标准转换,歧义

应通过更明确的函数签名、explicit 转换构造函数或调用处 static_cast 消除歧义。

结构化绑定与 std::string_view

C++17 结构化绑定让 tuple-like、聚合和 map 元素的解包更清晰:

cpp
for (const auto& [name, score] : scores) {
    // score 对 map 元素的 value 是 const 引用视图
}

绑定说明符决定是否复制:auto [a, b] 通常创建副本,auto& [a, b] 绑定原对象。

std::string_view 是非拥有的字符范围,适合只读参数、解析和切片,避免复制:

cpp
void parse(std::string_view input); // 调用期间立即使用通常安全

它必须不比底层字符存活更久。不要返回局部 std::string 的 view,也不要保存指向临时 std::string、可能 reallocate 的字符串或已释放内存的 view。字符串字面量有静态存储期;由长期存活的 std::string 创建的 view 在原字符串未销毁且未导致重新分配的修改前有效。

使用 Markdown 与 VitePress 构建