现代C++核心特性——内存篇

"modern C++(现代 C++)"这个概念通常是相对"经典 C++"(Classic C++)而言的,主要指从 C++11 开始的一系列语言标准,它们在语法、性能、安全性、并发等方面做出了非常大的改进。是 C++ 历史上最重大的版本更新之一,被称为 "现代C++的起点"。

其主要改进可以分为以下几个方面:

本篇主要讲解内存 部分主要特性,往期博客链接:
现代C++核心特性------语法篇-CSDN博客

现代C++核心特性------类型篇-CSDN博客


1 智能指针

(std::shared_ptr, std::unique_ptr, std::weak_ptr)

在传统 C++ 中,我们通常这样动态分配内存:

cpp 复制代码
int* p = new int(5);
delete p;

如果忘记 delete,就会导致内存泄漏 ;如果多次delete,就会产生悬空指针程序崩溃 。为了解决这些问题,C++11 引入了智能指针类,它能在对象不再使用时自动释放内存,从而减少内存泄漏、悬空指针等错误。

智能指针本质上是一个类模板 ,它内部包装了一个普通指针,并在析构时自动调用 delete。

std::unique_ptr ------ 独占所有权

  • 同一时间只能有一个指针指向某个对象
  • 禁止拷贝,只能 移动(move,见1.1.4中移动语义)

std::shared_ptr ------ 共享所有权

  • 多个智能指针可以共享同一块堆内存
  • 内部维护一个引用计数器(reference count)
  • 当最后一个指针销毁时,才释放内存

std::weak_ptr ------ 弱引用

  • shared_ptr 配合使用
  • 不增加引用计数
  • 用于打破循环引用

2 移动语义

在传统 C++ 中,复制对象时往往会造成性能浪费:

cpp 复制代码
std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = v1;  // 调用拷贝构造函数,复制所有元素

如果我们知道 v1 之后不再使用,其实没必要复制,可以"搬运"资源过去,这就是 移动语义

cpp 复制代码
std::vector<int> v3 = std::move(v1); // 调用移动构造函数

此时:

  1. 调用了 std::vector 的 移动构造函数

  2. v3 接管 了 v1 的内部资源(例如那块存放元素的堆内存指针);

  3. v1 被"掏空"了,但 并没有被销毁 。(v1 仍然处于可用状态 :可以安全地调用它的方法、重新赋值、销毁,但它的内容(数据)已经被转移走了,通常处于空或未定义但合法的状态)

内部机制:std::move( )将一个左值显式地转换为右值引用,从而启用移动语义。

作用:解决拷贝效率低的问题,显著提升 STL 容器的性能(如 vector、string)。

在这里补充介绍一下右值引用的概念:

cpp 复制代码
//举个例子:
int a = 10;
int &&r1 = 10;   // ✅ 合法:10 是右值
// int &&r2 = a; // ❌ 错误:不能绑定到左值

万能引用(T&&)是模板推导中一种特殊的右值引用,它可以根据实参类型自动折叠为左值引用或右值引用,是实现完美转发的关键。

出现条件:

cpp 复制代码
// 1. 类型是模板参数形式的 T&&
template <typename T>
void func(T&& arg);   // ✅ 满足条件
// 2. 参数的类型由模板推导得出(不是显式指定类型)
int x = 10;
func(x);      // ✅ T 被推导
func(10);     // ✅ T 被推导
func<int>(10); // ❌ 显式指定T=int,此时是右值引用,不是万能引用

但实际编程过程中,右值很少显式使用,多用于服务移动语义(std::move 和**完美转发(std::forward)**功能,移动语义我们刚才已经讲过了,接下来再来看看完美转发。

有时候我们写一个包装函数(wrapper),希望它把参数原封不动地传递给另一个函数:

cpp 复制代码
void process(int& x)      { cout << "左值引用版本\n"; }
void process(int&& x)     { cout << "右值引用版本\n"; }

template<typename T>
void wrapper(T arg) {       // ❌ 错误示范
    process(arg);           // 无论传什么,arg 都是左值!
}

包装函数vs普通函数

结合模板和右值引用,实现参数的高效转递:

cpp 复制代码
template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg)); // ✅ 完美转发
}
int a = 5;
wrapper(a);    // 输出:左值引用版本
wrapper(10);   // 输出:右值引用版本

内部机制:引用折叠规则 让模板可以自动区分左值和右值,从而实现完美转发。


不断学习,持续成长,欢迎大家一起交流学习!

相关推荐
lly2024062 小时前
C# 继承
开发语言
August_._2 小时前
【JAVA】基础(一)
java·开发语言·后端·青少年编程
软件开发技术深度爱好者2 小时前
Python类中方法种类介绍
开发语言·python
麦麦鸡腿堡2 小时前
Java_LinkedList底层结构
java·开发语言
沐怡旸2 小时前
【穿越Effective C++】条款13:以对象管理资源——RAII原则的基石
c++·面试
whatever who cares3 小时前
android/java中gson的用法
android·java·开发语言
一个不知名程序员www3 小时前
算法学习入门---二分查找(C++)
c++·算法
周杰伦fans3 小时前
C# 中 Entity Framework (EF) 和 EF Core 里的 `AsNoTracking` 方法
开发语言·c#
小灰灰搞电子3 小时前
Rust Slint实现控件尺寸的扩展与收缩源码分享
开发语言·后端·rust