智能指针,是真实原始指针被包装。使用智能指针时,将调用new并分配给你内存,这块内存在某些时候会自动释放
unique pointer 唯一指针#
在一个作用域内,同一个原始指针只能被一个 unique_ptr 管理。它禁止拷贝,这意味着你不能通过赋值来增加它的引用,只能使用 std::move() 将所有权从一个指针转移给另一个。
转让所有权-附加知识点#
#include <iostream>
#include <memory>
class Entity {};
int main() {
// 1. 创建第一个 unique_ptr
std::unique_ptr<Entity> entity = std::make_unique<Entity>();
// 2. 使用 std::move 转移所有权
// 此时,entity 原有的内存所有权交给了 e2
std::unique_ptr<Entity> e2 = std::move(entity);
// 3. 检查状态
if (entity == nullptr) {
std::cout << "entity 现在是空的 (nullptr)" << std::endl;
}
if (e2 != nullptr) {
std::cout << "e2 现在拥有 Entity 的所有权" << std::endl;
}
std::cin.get();
return 0;
} // e2 在这里离开作用域,Entity 被自动销毁
示例-主要知识点#
#include <iostream>
#include <string>
#include <memory>
class Entity
{
public:
//constructor-构造函数
Entity()
{
std::cout << "Created Entity!" << std::endl;
}
//destructor-析构函数
~Entity()
{
std::cout << "Destroyed Entity!" << std::endl;
}
void Print()
{
std::cout << "hello" << std::endl;
}
};
int main()
{
std::cout << "start--" << std::endl;
{
//不允许,禁止隐式转换
//std::unique_ptr<Entity> entity =new Entity();
//允许,直接调用构造函数,但是不建议
//如果创建完原始对象后发生了异常,没有来得及将它放到智能指针对象
//会导致不释放指针指向内存,导致内存泄漏
//std::unique_ptr<Entity> entity(new Entity());
//不会有这问题,因为std::make_unique保证了创建对象和将其
// 放入智能指针是一个原子操作==,中间不会被其他逻辑插入,因
// 此它是异常安全的。
std::unique_ptr<Entity> entity = std::make_unique<Entity>();
entity->Print();
std::unique_ptr<Entity> e2 = std::move(entity);//允许
//std::unique_ptr<Entity> e3 = entity;//不允许,编译报错
/*查看源码
* 复制构造函数和赋值操作符被删除
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
*/
}
std::cout << "end--" << std::endl;
std::cin.get();
}
/*
start--
Created Entity!
hello
Destroyed Entity!
end--
*/不允许std::unique_ptr<Entity> entity =new Entity(); 因为禁止隐式转换

为什么使用std::make_unique#
异常安全性(最核心的区别)
在复杂的表达式中,std::unique_ptr<Entity> entity(new Entity()); 写法可能会导致内存泄漏。
假设你有这样一个函数: void Function(std::unique_ptr<Entity> e, void(*DoSomething)())
如果你这样调用它: Function(std::unique_ptr<Entity>(new Entity()), PossibleExceptionFunc());
风险: C++ 标准并不规定参数的计算顺序。如果编译器先执行了 new Entity(),接着执行了 PossibleExceptionFunc(),而后者抛出了异常,那么此时 unique_ptr 还没来得及构造。
后果: 刚刚 new 出来的内存就再也没有人能释放它了,导致内存泄漏。
解决: std::make_unique 保证了创建对象和将其放入智能指针是一个原子操作,中间不会被其他逻辑插入,因此它是异常安全的。
share pointer 共享指针#
- unique_ptr: 内部只包含一个原始指针。它不维护任何额外的数据。在编译优化后,它的机器码和原始指针几乎一模一样。
- shared_ptr: 必须维护一个控制块 (Control Block)。这个控制块包含:引用计数(Reference Count)、弱引用计数(Weak Count)、其他元数据(如自定义删除器)每次拷贝或销毁shared_ptr,都要去更新这个计数。 当没有任何引用指向它时,才会delete原对象
共享指针的工作原理,是通过引用计数,引用计数是一种跟踪你有多少个引用指向那个指针,当引用计数达到0时,指针被delete
例子#
创建一个共享指针,然后创建另一个共享指针并复制引用,现在引用计数是两个。当第一个共享指针消失后计数减一,当最后一个共享指针消失后计数为零,引用消失,内存被释放
#include <iostream>
#include <string>
#include <memory>
class Entity
{
public:
//constructor-构造函数
Entity()
{
std::cout << "Created Entity!" << std::endl;
}
//destructor-析构函数
~Entity()
{
std::cout << "Destroyed Entity!" << std::endl;
}
void Print()
{
std::cout << "hello" << std::endl;
}
};
int main()
{
//这里e1不增加引用计数,所以
std::weak_ptr<Entity> e1;
std::cout << "start--" << std::endl;
{
std::cout << "start0--" << std::endl;
std::shared_ptr<Entity> e0;
{
std::cout << "start1--" << std::endl;
std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
e1= sharedEntity;
e0 = sharedEntity;
std::cout << "end1--" << std::endl;
}
//至此sharedEntity已经被回收,但是e0还在
std::cout << "end0--" << std::endl;
}
//至此e0已经被回收,所有的指向Entity对象的引用都被回收了,e1虽然没有被回收但是它不增加引用计数,所以Entity对象被回收
std::cout << "end--" << std::endl;
std::cin.get();
}
/* end0打印后,Entity对象才被回收
start--
start0--
start1--
Created Entity!
end1--
end0--
Destroyed Entity!
end--
*/weak指针的作用-补充#
循环引用的问题#
#include <memory>
#include <iostream>
struct B; // 前置声明
struct A {
A()
{
std::cout << "堆内存a1 创建了~" << std::endl;
}
std::shared_ptr<B> ptrB;
~A() { std::cout << "堆内存a1 销毁了\n"; }
};
struct B {
B()
{
std::cout << "堆内存b1 创建了~" << std::endl;
}
std::shared_ptr<A> ptrA;
~B() { std::cout << "堆内存b1 销毁了\n"; }
};
void test() {
std::cout << "---1" << std::endl;
auto a = std::make_shared<A>();//假设引用了堆内存a1
std::cout << "---2" << std::endl;
auto b = std::make_shared<B>();//假设引用了堆内存b1
a->ptrB = b; // a.ptrB 又引用 b1
b->ptrA = a; // b.ptrA 又引用 a1
//打印目前引用计数
std::cout << "a1被引用次数:" << a.use_count() << std::endl;
std::cout << "a1被引用次数:" << b->ptrA.use_count() << std::endl;
std::cout << "b1被引用次数:" << b.use_count() << std::endl;
std::cout << "b1被引用次数:" << a->ptrB.use_count() << std::endl;
}//如果struct B的成员ptrA为share_ptr,则:函数结束,a 和 b 本该被销毁,但由于互相引用,a,b的析构函数执行了,但是a1,b1的析构函数永远不会执行!因为有多个引用指向a1,b1
int main()
{
test();
//这里结束后 栈内存a,b都销毁了
std::cout << "函数调用结束" << std::endl;
std::cin.get();
return 0;
}
/*
---1
堆内存a1 创建了~
---2
堆内存b1 创建了~
a1被引用次数:2
a1被引用次数:2
b1被引用次数:2
b1被引用次数:2
函数调用结束
//这里函数调用结束后 栈内存a,b都销毁了
*/这里有个地方要注意
- 局部变量销毁了,意味着什么? 在 test() 函数结束时,栈上的局部变量 a 和 b 会自动失效。
- 当 a 销毁时,它会调用析构函数(不是对象A的析构函数,是
std::shared_ptr<Entity>的析构函数),动作是:“让它指向的 A 对象的引用计数减 1”。 - 当 b 销毁时,它会调用析构函数(不是对象B的析构函数,是
std::shared_ptr<Entity>的析构函数 ),动作是:“让它指向的 B 对象的引用计数减 1”。
- 关键在于计数没有归零
- A 对象最初计数是 2。a 销毁后,计数减 1,还剩下 1(因为 b->ptrA 还指着它)。
- B 对象最初计数是 2。b 销毁后,计数减 1,还剩下 1(因为 a->ptrB 还指着它)。
总结:
- 局部变量(栈):确实销毁了。
- 对象实体(堆):因为计数没归零,被漏掉了。
std::shared_ptr 如何在计数归零时触发 delete#
简单来说: 每一个 shared_ptr 析构时都会“问”一下堆里的控制块:“我是最后一个吗?”。如果是,它就负责“收尸”(执行 delete);如果不是,它就安静地走开。即先触发shared_ptr的析构函数,再决定是否delete堆内存(即堆内存对象的析构函数)
解决办法#
只修改了struct B 中的某一行,下面有注释
#include <memory>
#include <iostream>
struct B; // 前置声明
struct A {
A()
{
std::cout << "堆内存a1 创建了~" << std::endl;
}
std::shared_ptr<B> ptrB;
~A() { std::cout << "堆内存a1 销毁了\n"; }
};
struct B {
B()
{
std::cout << "堆内存b1 创建了~" << std::endl;
}
//std::shared_ptr<A> ptrA;//删除并添加下一行!!
std::weak_ptr<A> ptrA;//这里仅仅修改了这行!!
~B() { std::cout << "堆内存b1 销毁了\n"; }
};
void test() {
std::cout << "---1" << std::endl;
auto a = std::make_shared<A>();//假设引用了堆内存a1
std::cout << "---2" << std::endl;
auto b = std::make_shared<B>();//假设引用了堆内存b1
a->ptrB = b; // a.ptrB 又引用 b1
b->ptrA = a; // b.ptrA 又引用 a1
//打印目前引用计数
std::cout << "a1被引用次数:" << a.use_count() << std::endl;
std::cout << "a1被引用次数:" << b->ptrA.use_count() << std::endl;
std::cout << "b1被引用次数:" << b.use_count() << std::endl;
std::cout << "b1被引用次数:" << a->ptrB.use_count() << std::endl;
}//如果struct B的成员ptrA为share_ptr,则:函数结束,a 和 b 本该被销毁,但由于互相引用,a,b的析构函数执行了,但是a1,b1的析构函数永远不会执行!因为有多个引用指向a1,b1
int main()
{
test();
//这里结束后 栈内存a,b都销毁了
std::cout << "函数调用结束" << std::endl;
std::cin.get();
return 0;
}
/*
---1
堆内存a1 创建了~
---2
堆内存b1 创建了~
a1被引用次数:1
a1被引用次数:1
b1被引用次数:2
b1被引用次数:2
堆内存a1 销毁了
堆内存b1 销毁了
函数调用结束
*/分析堆内存a,b;及栈内存a1,b1的销毁顺序#
当 test() 函数运行到结束的大括号 } 时,栈内存开始清理:
栈内存 b 销毁:它是最后定义的,所以最先析构。
- 此时 b1 的强引用计数从 2 降为 1(因为 a1->ptrB 还指着它)。
- 结果:堆内存 b1 此时不执行析构函数。
栈内存 a 销毁:它是第一个定义的,最后析构。
- 此时 a1 的强引用计数从 1 降为 0(因为 b1->ptrA 是 weak_ptr,不计入强引用)。
- 结果:由于计数归零,堆内存 a1 开始执行析构函数。
堆内存 a1 销毁过程:
- 在 a1 的析构过程中,它的成员变量 ptrB(是一个 shared_ptr)也会随之销毁。
- ptrB 销毁导致 b1 的强引用计数从 1 降为 0。
- 结果:由于计数归零,堆内存 b1 紧接着执行析构函数。
解释“在 a1 的析构过程中,它的成员变量 ptrB(是一个 shared_ptr)也会随之销毁。”#
析构函数的“幕后工作”
在 C++ 中,一个类的析构过程分为两步,这是由编译器自动完成的:
- 执行析构函数体:也就是你写的 { std::cout « “…” « std::endl; } 里面的代码。
- 执行成员变量的析构(自动发生):在你的函数体执行完后,编译器会自动按照声明顺序的逆序,调用所有成员变量的析构函数。
逻辑流程如下:
~A() {
// 1. 先执行你写的代码
std::cout << "堆内存a1 销毁了\n";
// 2. 编译器暗中帮你执行:
// ptrB.~shared_ptr<B>();
}附上struct A的定义
struct A {
A()
{
std::cout << "堆内存a1 创建了~" << std::endl;
}
std::shared_ptr<B> ptrB;
~A() { std::cout << "堆内存a1 销毁了\n"; }
};作为观察者存在#
#include <iostream>
#include <memory>
class Entity
{
public:
//constructor-构造函数
Entity()
{
std::cout << "Created Entity!" << std::endl;
}
//destructor-析构函数
~Entity()
{
std::cout << "Destroyed Entity!" << std::endl;
}
void Print()
{
std::cout << "hello" << std::endl;
}
};
int main() {
std::weak_ptr<Entity> observer;
{
std::shared_ptr<Entity> entity = std::make_shared<Entity>();
observer = entity; // observer 记住了这个 entity
// 此时 entity 还在
if (auto locked = observer.lock()) { // lock() 尝试转成 shared_ptr
std::cout << "对象还在,正在调用 Print...\n";
locked->Print();
}
} // 出了这个大括号,entity 被销毁了
// 此时 entity 已经不在了
if (auto locked = observer.lock()) {
locked->Print();
}
else {
std::cout << "对象已经销毁了,无法调用!\n";
}
std::cin.get();
return 0;
}
/*
Created Entity!
对象还在,正在调用 Print...
hello
Destroyed Entity!
对象已经销毁了,无法调用!
*/