Skip to content

关于 const 的一切

引言

const 是 C++ 语言中的一个关键字,用于声明常量。它可以用于变量、指针、引用、成员函数等,表示这些实体的值在初始化后不能被修改。正确使用 const 可以提高代码的可读性、安全性和优化性能。
本文将使用简单示例并结合 C++/WinRT、Windows App SDK、WinUI 3 实战,包括:

  • 顶层/底层 const、auto/模板推导影响
  • 成员函数 const 与 this 指针类型、基于 const 的重载、迭代器语义
  • mutable 与“逻辑常量性”、缓存场景
  • 返回值/生命周期、NRVO、const 抑制移动的影响
  • 内存布局与段(.rdata/栈/静态存储期),const 不改变对象布局
  • C++/WinRT/WinUI 3 实战:事件处理参数签名(IInspectable const&/Args const&)、VM 属性 getter const noexcept、hstring/param::hstring 选择、只读集合 IVectorView、span/array_view 的只读视图
  • API 设计清单与进阶示例(只读缓冲、可移动/只读接口分离)

什么时候使用 const

  1. 函数参数
    • 如果函数参数是一个指针或引用,并且函数不需要修改该参数的值,可以将其声明为 const。例如:
cpp
     void print(const string& str);
  1. 成员函数
    • 如果成员函数不修改类的成员变量,可以将其声明为 const。例如:
cpp
     class MyClass {
     public:
         void display() const;
     };
  1. 局部变量
    • 如果局部变量在初始化后不需要修改,可以将其声明为 const。例如:
cpp
     const int max_value = 100;
  1. 返回值
    • 如果函数返回一个指针或引用,并且不希望调用者修改返回的对象,可以将其声明为 const。例如:
cpp
     const string& getName() const;

示例

以下是一些使用 const 的示例:

cpp
#include <iostream>
#include <vector>

using namespace std;

void print(const vector<int>& vec) {
    for (const auto& elem : vec) {
        cout << elem << " ";
    }
    cout << endl;
}

class MyClass {
public:
    MyClass(int value) : value(value) {}

    void display() const {
        cout << "Value: " << value << endl;
    }

private:
    int value;
};

int main() {
    const int max_value = 100;
    vector<int> numbers = {1, 2, 3, 4, 5};

    print(numbers);

    MyClass obj(42);
    obj.display();

    return 0;
}

在这些示例中,const 关键字用于函数参数、成员函数、局部变量和循环变量,确保这些变量在使用过程中不会被修改。

深入理解 const:函数运行原理、类型系统与内存布局

下面从 C++ 类型系统、调用约定、对象模型与 C++/WinRT(Windows App SDK、WinUI 3)实践的角度深入理解 const

1) 顶层/底层 const 与类型推导

  • 顶层(top-level)const:限定对象本身不能被重新赋值。
    • int* const pp 是常量指针(指针本身不可变),但指向的 int 可变。
  • 底层(low-level)const:限定对象指向/引用的目标不可变。
    • int const* pconst int* p:指向常量 int 的指针(指向目标不可修改)。
  • 引用总是底层 const 的别名:int const& r = x; 表示通过 r 不能改 x
  • 模板与 auto 推导:
    • auto x = expr; 会去掉顶层 constauto&/auto const& 保留底层 const
    • 模板参数 TT const& 仅推导 T,底层 const 体现在引用层。

要点:顶层 const 影响“能否给变量重绑定/赋值”,底层 const 影响“能否通过该别名修改目标”。

2) 成员函数的 const 与 this 指针

  • void f() const 的隐式 this 类型为 MyClass const* const
    • 成员函数内不能修改非 mutable 成员;
    • 不能把 this 重新绑定到别处(顶层 const)。
  • 可以对同名函数基于 const 重载:
cpp
struct Buffer {
    uint8_t* data();          // 可变访问
    uint8_t const* data() const; // 只读访问
};
  • 引用限定符与 const 配合:
    • T&& 成员函数(右值限定)常用于启用移动;const 会禁止移动(见下文)。

3) 迭代器与容器的 const 语义

  • begin()begin() const 返回不同类型:非常量对象给可写迭代器,常量对象给只读迭代器。
  • 为自定义容器提供成对的 const/非 const 访问器有助于“只读视图”。

4) mutable 与“逻辑常量性”

  • mutable 成员允许在 const 成员函数中被修改,用于缓存、统计计数等逻辑不破坏抽象不变量的场景。
  • 很多带引用计数的类型(如字符串、小对象优化容器)内部的计数实现通常被视为逻辑常量性的一部分; 在 C++/WinRT 的 winrt::hstring 中,返回值拷贝成本低(共享底层 HSTRING)。

5) 返回值、生命周期与移动

  • 不要返回“指向局部对象的 const&”——悬垂引用。
  • 给临时对象绑定 const& 可延长临时生命周期到该引用作用域结束:
cpp
const std::string& r = std::string("tmp"); // OK,临时延长到 r 的作用域结束
  • “返回 const 值”的反模式:

    • const T f(); 对调用者几乎无益,反而妨碍移动(不能对 const 值调用移动赋值/构造)。
    • 更佳:直接返回 T(值),由编译器进行返回值优化(NRVO)或移动。
  • const 会抑制移动:

cpp
std::string s;
std::string const sc;
auto x = std::move(s);  // 移动
auto y = std::move(sc); // 仍然是拷贝,因为对象是 const
  • 设计参数时:只读大对象用 T const&,可转移所有权用 T(按值)或 T&&,避免把需要移动的值声明为 const

6) 内存布局与段(MSVC/Windows 视角)

  • const 不改变对象的内存布局(字段顺序、vptr、对齐均不变)。
  • 作用域与存储期:
    • 局部 const 通常在栈上;
    • 具有静态/全局存储期的 const(尤其带内部链接)常放在只读节(如 .rdata);
    • 字符串字面量放在只读节,共享存储,修改是未定义行为。
  • mutable 成员与常量对象同样位于对象内存中;对象整体不一定在只读节,是否放入只读节由存储期与链接属性决定,而非类型的 const 与否。

7) 与 C++/WinRT、Windows App SDK、WinUI 3 的实践

  • 事件处理签名采用只读引用,避免拷贝:
cpp
void MainWindow::OnClick(
    winrt::Windows::Foundation::IInspectable const& sender,
    winrt::Microsoft::UI::Xaml::RoutedEventArgs const& e)
{
    // sender/e 语义上只读;通常无需也不应修改
}
  • 属性(Property)/绑定(Binding):
    • ViewModel 的只读 getter 建议 const noexcept,表达只读与不抛异常:
cpp
struct PersonViewModel : winrt::implements<PersonViewModel, winrt::Windows::UI::Xaml::Data::INotifyPropertyChanged>
{
    winrt::hstring Name() const noexcept { return m_name; } // 按值返回,hstring 开销低
    void Name(winrt::hstring const& v) { if (m_name != v) { m_name = v; RaiseChanged(L"Name"); } }
private:
    winrt::hstring m_name;
};
  • hstring 按值返回通常高效;对大型缓冲区参数,优先使用视图:

    • 文本:winrt::param::hstringwinrt::hstring const&
    • 数组/缓冲:winrt::array_view<T const>std::span<T const>
  • 只读集合视图:

    • WinRT 接口按值返回智能句柄式对象(拷贝成本低),但可通过只读接口暴露:
cpp
winrt::Windows::Foundation::Collections::IVectorView<int> Items() const; // 只读视图
  • IDL/ABI 边界:

    • WinRT IDL 不存在 C++ 的 const 成员函数概念;只读通过 readonly 属性表达;
    • C++/WinRT 投影会将逻辑只读的成员生成为 const 成员函数,便于在 C++ 侧进行 const 校验与重载。
  • 线程与 UI 亲和性:

    • const 不等于线程安全;WinUI 3 UI 类型需要在 UI 线程访问;
    • 即使成员函数是 const,若触及 UI 状态仍需切回 UI 线程(如 co_await winrt::resume_foreground(DispatcherQueue()))。
  • Lambda 捕获与事件:

    • 在事件回调中优先捕获只读引用或值的只读视图,避免不必要拷贝与数据竞争;
    • auto const handler = [...] { ... }; 明确只读意图。

8) API 设计清单(结合 WinUI 3/C++/WinRT)

  • 输入参数:
    • 文本:winrt::param::hstring(最灵活),或 winrt::hstring const&
    • 缓冲:std::span<T const>winrt::array_view<T const>
    • 大对象:T const&;小标量按值。
  • 返回值:
    • 值语义类型(如 hstring、智能句柄、WinRT 接口)按值返回;
    • 大型内部容器可返回只读视图接口或 std::span<T const>
    • 避免 const T(值)返回类型。
  • 成员函数:
    • 只读操作标记 const noexcept
    • 提供成对的 const/非 const 访问器以区分读写;
    • 需要缓存时用 mutable 并保证线程安全。

9) 进阶示例

  • 指针/引用 const 组合:
cpp
int x = 0;
int* const p1 = &x;      // 指针本身不可变
int const* p2 = &x;      // 指向只读 int
int const* const p3 = &x;// 指针与目标都只读

void foo(int const& r);
foo(x); // 通过只读引用传参
  • 只读缓冲参数(WinRT/C++20):
cpp
void Process(std::span<uint8_t const> bytes);
// 或 C++/WinRT 传统写法:
void Process(winrt::array_view<uint8_t const> bytes);
  • 事件处理与绑定:
cpp
void MainWindow::OnSelectionChanged(
    winrt::Windows::Foundation::IInspectable const& sender,
    winrt::Microsoft::UI::Xaml::Controls::SelectionChangedEventArgs const& e)
{
    // 读取 e.AddedItems() 等,不修改 e 本身
}
  • 可移动与 const:
cpp
std::vector<int> Build();              // 返回值可移动/优化
void Use(std::vector<int> v);          // 需要所有权时按值接受
void Read(std::vector<int> const& v);  // 只读访问

结论:

  • const 是类型系统的组成部分,既影响重载/推导,也影响接口设计与可读性;
  • 它不改变对象布局或调用约定,但可能影响优化(启用只读路径、消除写屏障);
  • 在 C++/WinRT、WinUI 3 中,合理运用 const(只读 getter、参数视图、事件只读引用)可提升 API 清晰度与效率,同时要避免用 const 阻断必要的移动与可变操作。