C++指针演进史:从std::unique_ptr到std::make_unique的演变
发布时间: 2024-10-23 11:18:55 阅读量: 15 订阅数: 25
![C++指针演进史:从std::unique_ptr到std::make_unique的演变](https://img-blog.csdnimg.cn/20210620161412659.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h1bnllX2RyZWFt,size_16,color_FFFFFF,t_70)
# 1. C++指针基础回顾
## 1.1 指针的基础概念
在C++编程语言中,指针是一个基础但又十分强大的特性。指针存储了变量的内存地址,这使得程序员能够直接操作内存,以执行如动态内存分配等高级操作。尽管如此,直接操作内存地址也带来了风险,比如内存泄漏和野指针问题。随着C++的发展,智能指针应运而生,旨在帮助开发者更安全、更方便地管理内存。
## 1.2 指针与数组的关系
指针与数组是紧密相关的,指针可以用来遍历数组,而数组名在大多数情况下可以被当作指针使用。例如,一个数组可以通过指针来进行索引操作。理解这一点对于深入掌握C++指针是非常重要的,也为理解智能指针提供了基础。
## 1.3 指针与函数的互动
指针同样可以被用于函数中,既可以作为函数的参数传递复杂数据类型的地址,也可以通过指针返回多个值。这一特性使得函数能够操作实际的数据,而不仅仅是在函数内部处理值的副本。对于理解如何在C++中有效使用智能指针来说,这是另外一个关键点。
```cpp
void exampleFunction(int* ptr) {
if (ptr) {
*ptr = 10; // 修改指针指向的值
}
}
int main() {
int a = 5;
exampleFunction(&a); // 传递指针
// a 现在是 10
return 0;
}
```
在上述代码中,`exampleFunction`接收一个整型指针作为参数,并通过解引用操作符`*`修改了指针指向的值。这展示了函数与指针之间的基本互动方式。通过掌握这些基础知识,我们将能够更好地理解后续章节中智能指针的高级用法。
# 2. 智能指针std::unique_ptr的诞生
### 2.1 智能指针的理论基础
#### 2.1.1 智能指针与手动内存管理
智能指针是C++标准库提供的一类特殊的指针类模板,旨在简化动态内存管理,并解决手动分配和释放内存带来的潜在风险。在C++中,手动管理内存通常涉及使用`new`和`delete`操作符来分配和释放内存。这种方式虽灵活,但容易出错,容易导致内存泄漏和重复释放等问题。
为了防止这类问题,智能指针应运而生。`std::unique_ptr`是其中最基本的一种,它封装了原始指针,并在`std::unique_ptr`对象销毁时自动释放所管理的内存。这种自动释放内存的行为减少了忘记`delete`的风险,从而提高了程序的安全性。
```cpp
// 示例:std::unique_ptr的基本用法
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> p = std::make_unique<int>(10);
std::cout << *p << std::endl; // 输出:10
// 离开作用域,p被销毁,管理的内存自动释放
return 0;
}
```
上述代码中,我们创建了一个`std::unique_ptr`对象`p`,并使用`std::make_unique`来初始化它。这样我们就不需要手动调用`delete`来释放内存,当`p`的生命周期结束时,它所管理的内存会自动被释放。
#### 2.1.2 std::unique_ptr的定义和特性
`std::unique_ptr`属于RAII(Resource Acquisition Is Initialization)资源获取即初始化原则的应用,这种设计模式通过构造函数获取资源,在析构函数释放资源,从而确保资源的生命周期得到妥善管理。
`std::unique_ptr`的特点是拥有它所指向的资源。它不允许复制构造函数(拷贝构造函数已被删除),但允许通过移动语义将资源的所有权从一个`std::unique_ptr`转移到另一个。当`std::unique_ptr`被销毁时,它所指向的资源也会随之销毁。
```cpp
// 示例:std::unique_ptr不允许复制构造,允许移动构造
#include <iostream>
#include <memory>
void process(std::unique_ptr<int>&& p) {
std::cout << *p << std::endl;
}
int main() {
std::unique_ptr<int> p1 = std::make_unique<int>(10);
std::unique_ptr<int> p2 = std::move(p1); // 使用移动语义转移所有权
// p1现在为空,p2拥有资源的所有权
process(std::move(p2)); // 将资源所有权传递给process函数
// p2现在为空
return 0;
}
```
在上面的代码示例中,`std::unique_ptr<int>`被移动构造后,资源的所有权从`p1`转移到了`p2`。随后,`p2`的所有权再次被转移给`process`函数。值得注意的是,移动后原`std::unique_ptr`将不再拥有资源,从而避免了资源的双重释放。
### 2.2 实践中的std::unique_ptr
#### 2.2.1 使用std::unique_ptr管理内存
使用`std::unique_ptr`来管理动态分配的内存资源是一种安全且方便的方法。它比手动管理内存更可靠,因为`std::unique_ptr`保证了资源的自动释放,即使在发生异常的情况下也不会导致内存泄漏。
```cpp
#include <iostream>
#include <memory>
#include <stdexcept>
// 模拟一个可能抛出异常的函数
void riskyOperation() {
throw std::runtime_error("Exception occurred!");
}
int main() {
std::unique_ptr<int> p = std::make_unique<int>(10);
try {
riskyOperation();
std::cout << *p << std::endl; // 如果没有异常发生,则输出10
} catch (...) {
// 如果抛出异常,p会自动销毁,所指向的内存会被释放
}
return 0;
}
```
在上述示例中,如果`riskyOperation`函数抛出异常,由于`p`是一个`std::unique_ptr`,所以它会自动销毁,所指向的内存资源会得到释放,避免了内存泄漏的风险。
#### 2.2.2 std::unique_ptr与异常安全
异常安全是C++编程中必须考虑的问题,特别是在资源管理方面。异常安全涉及在异常发生时,是否能保持对象状态的一致性,以及是否能清理已经分配的资源,避免资源泄漏。
由于`std::unique_ptr`在其析构函数中释放资源,它天然支持异常安全。无论是函数正常返回,还是因为异常而退出,`std::unique_ptr`都会确保资源被正确释放。
```cpp
#include <iostream>
#include <memory>
#include <stdexcept>
void potentiallyThrowingFunction() {
throw std::runtime_error("An exception was thrown!");
}
void doSomethingWithUniquePtr(std::unique_ptr<int>& ptr) {
try {
potentiallyThrowingFunction();
} catch (...) {
// 异常发生,即使在函数内部,资源也会被std::unique_ptr自动释放
}
}
int main() {
std::unique_ptr<int> ptr = std::make_unique<int>(42);
doSomethingWithUniquePtr(ptr);
// 如果异常发生,ptr的析构函数将被调用,释放资源
return 0;
}
```
上面的代码中,即使`potentiallyThrowingFunction`抛出了异常,`std::unique_ptr`会自动清理其管理的资源,确保异常安全。
#### 2.2.3 std::unique_ptr在STL容器中的应用
`std::unique_ptr`可以存储在标准模板库(STL)容器中,如`std::vector`或`std::list`。这提供了一种方便的方式来管理一个动态对象集合。
然而,需要注意的是,不能在STL容器中复制`std::unique_ptr`,因为复制构造函数已被删除。但可以通过插入`std::unique_ptr`的副本(移动语义)到容器中,来间接地实现复制效果。
```cpp
#include <iostream>
#include <memory>
#include <vector>
int main() {
std::vector<std::unique_ptr<int>> vec;
vec.emplace_back(std::make_unique<int>(10));
vec.emplace_back(std::make_unique<int>(20));
// vec现在拥有两个std::unique_ptr对象
for (auto& p : vec) {
std::cout << *p << std::endl; // 输出10和20
}
return 0;
}
```
在这个例子中,我们使用`emplace_back`方法将`std::unique_ptr<int>`添加到了`std::vector`中。这是通过移动语义完成的,因此每个`std::unique_ptr`在容器中都是独立拥有的。
### 2.3 std::unique_ptr的局限性分析
#### 2.3.1 与std::shared_ptr的比较
`std::unique_ptr`和`std::shared_ptr`是两种不同的智能指针,它们在内存管理上各有优劣。`std::unique_ptr`的所有权是排他的,而`std::shared_ptr`允许多个指针共享资源的所有权,其引用计数跟踪有多少个`std::shared_ptr`对象共享同一资源。
`std::unique_ptr`适合那些
0
0