浅谈`std::experimental::propagate_const`

The matters you reflect upon have been previously deliberated by others.

常写C++的都知道,const用来声明变量或对象的不变性,但当涉及到指针或类指针对象的时候,这种不变性的约束似乎被打破了。

cpp 复制代码
// ptr 本身不能被修改
const std::unique_ptr<int> ptr = std::make_unique<int>(42);
// ptr 指向的对象(即 int 类型的值)仍然可以被修改,这是合法的
*ptr = 100;

更进一步的一个例子(godbolt)

cpp 复制代码
struct A
{
  void bar() const 
  { 
    std::cout << "bar (const)" << std::endl; 
  }
  
  void bar() 
  { 
    std::cout << "bar (non-const)" << std::endl; 
  }
};

struct B
{
  B() : m_ptrA(std::make_unique<A>()) {} 
  
  void foo() const 
  { 
    std::cout << "foo (const)" << std::endl;
    m_ptrA->bar(); 
  }           
  
  void foo() 
  { 
    std::cout << "foo (non-const)" << std::endl;
    m_ptrA->bar(); 
  }

  std::unique_ptr<A> m_ptrA;
};

int main()
{    
  B b;
  b.foo();
  
  const B const_b;
  const_b.foo();
}

得到的输出是:

arduino 复制代码
foo (non-const)
bar (non-const)
foo (const)
bar (non-const)

上述行为可以通过使用 const_cast 重写 void B::foo() const 来修正,以显式调用 A 的 const 成员函数。但这样的改动并不优雅。

有其他办法么?std::experimental::propagate_const

它来自于2015年的一份提案:A Proposal to Add a Const-Propagating Wrapper to the Standard Library

简言之:std::experimental::propagate_const 是一个包装器类,用于确保 const 属性不仅应用于指针或类指针对象本身,还应用于其所指向的对象。从而达到在 const 对象中传播 const 属性的功能。

还是用示例来描述:

示例一 (godbolt)

cpp 复制代码
#include <experimental/propagate_const>
#include <iostream>
#include <memory>

int main() {
    std::experimental::propagate_const<std::unique_ptr<int>> ptr = std::make_unique<int>(42);

    // 通过 const 访问路径访问时,ptr 被视为指向 const 的指针
    const auto& constPtr = ptr;
    std::cout << *constPtr << std::endl;  // 输出:42
    * constPtr = 50;        // 不合法,会有编译错误

    // 修改 ptr 指向的对象
    *ptr = 100;
    std::cout << *ptr << std::endl;  // 输出:100

    return 0;
}

会得到一个编译错误

shell 复制代码
# 编译输出
<source>: In function 'int main()':
<source>:11:16: error: assignment of read-only location '(& constPtr)->std::experimental::fundamentals_v2::propagate_const<std::unique_ptr<int> >::operator*()'
   11 |     * constPtr = 50;
      |     ~~~~~~~~~~~^~~~
Compiler returned: 1

示例二 (godbolt)

cpp 复制代码
#include <experimental/propagate_const>
#include <iostream>
#include <memory>
 
struct X
{
    void g() const { std::cout << "X::g (const)\n"; }
    void g() { std::cout << "X::g (non-const)\n"; }
};
 
struct Y
{
    Y() : m_propConstX(std::make_unique<X>()), m_autoPtrX(std::make_unique<X>()) {}
 
    void f() const
    {
        std::cout << "Y::f (const)\n";
        m_propConstX->g();
        m_autoPtrX->g();
    }
 
    void f()
    {
        std::cout << "Y::f (non-const)\n";
        m_propConstX->g();
        m_autoPtrX->g();
    }
 
    std::experimental::propagate_const<std::unique_ptr<X>> m_propConstX;
    std::unique_ptr<X> m_autoPtrX;
};
 
int main()
{
    Y y;
    y.f();
 
    const Y cy; // Y 的 const 属性,自然的传递到了其内部变量 m_propConstX 上
    cy.f();
}

得到输出

arduino 复制代码
Y::f (non-const)
X::g (non-const)
X::g (non-const)
Y::f (const)
X::g (const)
X::g (non-const)

std::experimental::propagate_const的一些特性:

  • 如果 propagate_const 包装的对象是 const,则其指向的对象也会被视为 const
  • 如果 propagate_const 包装的对象是非 const,则其指向的对象也是非 const
  • propagate_const 支持包装智能指针(如 std::unique_ptr 和 std::shared_ptr),也支持包装普通指针
  • 对指针指向的访问是透明的:propagate_const 重载了 operator* 和 operator->,使得访问包装的对象与直接访问指针一样方便

std::experimental::propagate_const的注意事项:

  • propagate_const 目前是 C++ 标准库的一个扩展,定义在 <experimental/propagate_const> 中),尚未正式纳入 C++ 标准
  • 需要确认编译器的支持情况,通常在C++17或更高版本的编译器上可用
  • propagate_const 是一个轻量级包装器,通常不会引入额外的性能开销

std::experimental::propagate_const的使用场景:

  • 确保对象及其成员(特别是成员中包含指针指向的间接成员)都是不可变时

参考引用:

相关推荐
sm_926787057 小时前
RFID 标签打印的技术实现要点与二次开发实践
java·大数据·前端·c++·编辑器
by209997 小时前
C++ 多态:同一个基类接口,调用不同的函数
开发语言·c++·经验分享·多态
colinu7 小时前
C++ 异步任务与线程生命周期:`std::async`、`future`、`thread`、`jthread` 与协作式退出
java·jvm·c++
wuminyu8 小时前
ForkJoinPool窃取线程在扫描其他WorkQueue时的随机采样算法与Phase线程挂起唤醒机制解析
java·linux·c语言·jvm·c++
longlongzihan8 小时前
链表找环:Floyd 判圈算法(LeetCode 141 & 142 )
c++·算法·leetcode·链表
liulilittle8 小时前
Linux 下 select 测试函数
linux·服务器·网络·数据库·c++·select·c
无忧.芙桃9 小时前
C++语言原理与实践(十二):list类的底层实现
c++·windows·list
沫璃染墨9 小时前
《Linux工程实践篇(二):线程池实战——从线程安全单例到完整源码实现》
linux·开发语言·c++·单例模式·设计模式·系统架构
有只小白叫岳飒10 小时前
c++智能指针 unique_ptr与函数调用
c++
Leo.yuan10 小时前
Kafka+Flink拼凑,还是FineDataLink 5.0一体化?实时数据处理的两种思路
c++·mfc