智能指针C++11
1-定义
在C++中,智能指针是一种用于自动管理动态分配内存的抽象机制。它们通过RAII(资源获取即初始化,Resource Acquisition Is Initialization)原则,帮助开发者自动管理资源生命周期,避免内存泄漏、双重释放等问题。
定义我们需了解,具体应用我会展示。
2-智能指针的类型
C++标准库中主要的智能指针包括:
1.unique_ptr
:独占所有权的智能指针。
2.shared_ptr
:共享所有权的智能指针。
3.weak_ptr
:配合shared_ptr
使用的非拥有型智能指针,用于解决循环引用问题。
其中包括auto_ptr 但是在C++17中已被移除,基本功能已被 unique_ptr所替代。
3-各指针用法
3.1 auto_ptr(选读)
虽然被取代但是 我们还需了解。
auto_ptr
的基本用法:
定义与特点
- 独占所有权:
auto_ptr
拥有对动态分配对象的唯一所有权。 - 拷贝语义:拷贝一个
auto_ptr
会转移所有权,而不是进行深拷贝。这意味着拷贝后,原来的auto_ptr
将不再拥有对象的所有权。 - 不可拷贝赋值:不能进行拷贝赋值操作,但可以通过拷贝构造函数进行所有权转移。
举例
int main() {
auto_ptr<int> ptr1(new int(10));
cout << "*ptr1 = " << *ptr1 << endl;
// 拷贝构造,转移所有权
auto_ptr<int> ptr2(ptr1);
if(ptr1.get() == nullptr) {
cout << "ptr1 is now empty after copy." << endl;
}
cout << "*ptr2 = " << *ptr2 << endl;
return 0;
}
//输出
//*ptr1 = 10
//ptr1 is now empty after copy.
//*ptr2 = 10
实现的内容:
ptr1
通过new int(10)
分配了一个整数对象。ptr2(ptr1)
通过拷贝构造转移了ptr1
的所有权,导致ptr1
不再拥有对象的所有权,其内部指针被置为nullptr
。- 这种所有权转移机制避免了双重释放的问题,但同时也带来了其他问题。
存在的问题:
1.不安全的拷贝语义:
auto_ptr
的拷贝操作会转移所有权,这可能导致意外的资源所有权转移,进而引发未定义行为或程序崩溃。- 例如,在函数参数传递或返回时,拷贝操作会转移所有权,可能导致原指针变为空。
2.不支持移动语义:
auto_ptr
在C++11之前没有移动语义的概念,这使得它在需要明确所有权转移的场景下不够灵活和安全。
3.不支持数组:
auto_ptr
只能管理单个对象,不能管理动态分配的数组。这限制了它的使用范围。
4.缺乏删除器支持:
auto_ptr
不支持自定义删除器,这使得它无法管理需要特殊释放逻辑的资源,如文件句柄、数据库连接等。
后续为何被取代
:
- 明确的移动语义:
unique_ptr
支持移动语义,明确了所有权转移的方式,避免了auto_ptr
的不安全拷贝问题。 - 支持数组:
std::unique_ptr
可以管理动态分配的数组,通过指定删除器可以正确释放数组内存。 - 支持自定义删除器:
unique_ptr
支持自定义删除器,可以管理不同类型的资源。 - 更好的性能和安全性:
unique_ptr
提供了更好的性能和类型安全性。
3.2 unique_ptr
定义与特点
- 独占所有权:
unique_ptr
拥有对动态分配对象的唯一所有权,不允许拷贝,只允许移动。 - 轻量级:由于不需要引用计数,
unique_ptr
的开销非常小。 - 不可拷贝:无法进行拷贝构造或拷贝赋值,但可以移动。
使用场景
- 用于管理独占资源的生命周期。
- 当不需要多个指针共享同一个对象时。
举例:
int main() {
// 使用 unique_ptr 管理动态分配的整数
unique_ptr<int> ptr1 = make_unique<int>(10);
cout << "*ptr1 = " << *ptr1 << endl;
// 移动所有权
unique_ptr<int> ptr2 = move(ptr1);
if(!ptr1) {
cout << "ptr1 is now empty." << endl;
}
cout << "*ptr2 = " << *ptr2 << endl;
return 0;
}
//输出
//*ptr1 = 10
//ptr1 is now empty.
//*ptr2 = 10
3.3shared_ptr
定义与特点
- 共享所有权:
shared_ptr
允许多个指针共享同一个对象,内部使用引用计数来管理对象的生命周期。 - 引用计数:每增加一个
shared_ptr
指向对象,引用计数加1;每销毁一个shared_ptr
,引用计数减1。当引用计数为0时,自动释放对象。 - 拷贝与赋值:可以拷贝和赋值,但需要注意循环引用问题。
使用场景
- 当多个指针需要共享同一个对象时。
- 需要在多个地方引用同一个动态分配的对象。
举例:
int main() {
// 使用 shared_ptr 管理动态分配的整数
shared_ptr<int> ptr1 = make_shared<int>(20);
{
shared_ptr<int> ptr2 = ptr1;
cout << "*ptr1 = " << *ptr1 << endl;
cout << "*ptr2 = " << *ptr2 << endl;
cout << "Reference count: " << ptr1.use_count() << endl; // 输出: 2
}
cout << "After inner scope, reference count: " << ptr1.use_count() << endl; // 输出: 1
cout << "*ptr1 = " << *ptr1 << endl;
return 0;
}
//输出
//*ptr1 = 20
//*ptr2 = 20
//Reference count: 2
//After inner scope, reference count: 1
//*ptr1 = 20
3.4 weak_ptr
定义与特点
- 非拥有型:
weak_ptr
不拥有对象的所有权,不增加引用计数。 - 配合
shared_ptr
使用:通常由shared_ptr
创建,用于解决循环引用问题。 - 访问对象:需要通过
lock()
方法获取一个shared_ptr
,以访问对象。
使用场景
- 当需要引用一个对象但不希望影响其生命周期时。
- 解决
shared_ptr
之间的循环引用问题。
举例:
//修改前的样例
class B;
class A {
public:
shared_ptr<B> ptr;
~A() { cout << "A destroyed" << endl; }
};
class B {
public:
weak_ptr<A> ptr; // 使用 weak_ptr 避免循环引用
~B() { cout << "B destroyed" << endl; }
};
int main() {
{
shared_ptr<A> a = make_shared<A>();
shared_ptr<B> b = make_shared<B>();
a->ptr = b;
b->ptr = a;
// 此时引用计数为2
}
// 离开作用域后,引用计数减为1,A 和 B 都不会被销毁
cout << "Objects still alive." << endl;
return 0;
}
//输出:Objects still alive.
解释:
- 这个例子中,
A
和B
互相持有shared_ptr
,导致引用计数无法降为0,从而产生内存泄漏。 - 如果将
B
中的shared_ptr<A>
改为weak_ptr<A>
,则可以避免循环引用
修改后:
class B;
class A {
public:
shared_ptr<B> ptr;
~A() { cout << "A destroyed" << endl; }
};
class B {
public:
weak_ptr<A> ptr; // 使用 weak_ptr 避免循环引用
~B() { cout << "B destroyed" << endl; }
};
int main() {
{
shared_ptr<A> a = make_shared<A>();
shared_ptr<B> b = make_shared<B>();
a->ptr = b;
b->ptr = a;
// 此时引用计数为2
}
// 离开作用域后,引用计数减为1,A 被销毁,B 也被销毁
cout << "After scope, objects destroyed." << endl;
return 0;
}
//输出:
//A destroyed
//B destroyed
//After scope, objects destroyed.
4 智能指针实现原理
1. unique_ptr
- 内部实现:通常包含一个指向对象的指针和一个删除器(deleter)。
- 移动语义:通过移动构造函数和移动赋值运算符转移所有权。
- 删除器:默认情况下,使用
delete
操作符释放资源,也可以自定义删除器以管理不同类型的资源。
2. shared_ptr
- 内部实现:包含一个指向对象的指针和一个指向控制块的指针。控制块包含引用计数和一个弱引用计数。
- 引用计数:每增加一个
shared_ptr
指向对象,引用计数加1;每销毁一个shared_ptr
,引用计数减1。 - 控制块:控制块在第一次创建
shared_ptr
时分配,包含引用计数和弱引用计数。 - 线程安全:引用计数和弱引用计数的操作是线程安全的。
3. weak_ptr
- 内部实现:包含一个指向控制块的指针,但不拥有对象的所有权。
- 弱引用计数:用于跟踪
shared_ptr
的引用计数,不影响对象的生命周期。 - 访问对象:需要通过
lock()
方法获取一个shared_ptr
,以访问对象。
5-使用及其注意事项
1.避免循环引用:
shared_ptr
之间互相持有会导致引用计数无法降为0,从而产生内存泄漏。- 使用
weak_ptr
可以解决循环引用问题。
2.选择合适的智能指针:
unique_ptr
用于独占所有权场景。shared_ptr
用于共享所有权场景。weak_ptr
用于需要引用对象但不影响其生命周期的情况。
3.自定义删除器:
- 可以为智能指针指定自定义删除器,以管理不同类型的资源,如文件句柄、数据库连接等。
4.避免不必要的拷贝:
unique_ptr
不支持拷贝,只能移动。shared_ptr
支持拷贝,但会增加引用计数,带来一定的开销。
5.性能考虑:
shared_ptr
的引用计数操作会带来一定的性能开销,应根据实际需求合理使用。
希望这篇文章有助于了解智能指针,如果有错误还请纠正