C++忘了学,学了忘,但是从未做过真正的项目,那为什么还会重复开头的场景呢?
因为一名Android工程师,C++是必备的技能,每一个产品,仔细去分析的时候都能发现很多模块都可以使用C++开发,从安全性、性能等方面都有帮助。
之前对于C/C++的影响就是指针难学,概念多,还有各种内存问题,着实很烦。
智能指针,用于管理动态分配内存,以帮助避免内存泄漏和管理资源的生命周期。智能指针是与原始指针不同的,它们可以自动地跟踪对象的所有权,当对象不再需要时,自动释放内存。 就问你怕不怕。
智能指针分类
在C++ 标准库中,有四个只能指针 即
- std::auto_ptr
- std::unique_ptr
- std::shared_ptr
- std::weak_ptr
其中std::auto_ptr在C++ 98 标准中就存在,目前已经废弃,所以不做学习。
std::unique_ptr
std::unique_ptr
是C++11引入的独占所有权智能指针。它允许一个对象拥有另一个对象的唯一所有权,当std::unique_ptr
超出作用域时,它会自动释放对象的内存。
c++
#include <memory>
std::unique_ptr<int> uniquePtr = std::make_unique<int>(42);
主要特点:
- 唯一所有权:每个
std::unique_ptr
实例拥有对象的唯一所有权。 - 高效且安全:没有额外的引用计数开销,适用于大多数情况。
- 可移动语义:可以使用
std::move
转移所有权。 - 不能共享:不能复制或共享所有权,因此不会导致资源泄漏
std::shared_ptr
std::shared_ptr
是C++11引入的共享所有权智能指针。它允许多个std::shared_ptr
实例共享同一个对象的所有权,当最后一个std::shared_ptr
超出作用域时,对象的内存将被释放。
c++
#include <memory>
std::shared_ptr<int> sharedPtr1 = std::make_shared<int>(42);
std::shared_ptr<int> sharedPtr2 = sharedPtr1; // 共享所有权
主要特点:
- 共享所有权:多个
std::shared_ptr
可以共享同一个对象的所有权。 - 引用计数:内部维护引用计数,自动释放对象。
- 适用于循环引用:可以与
std::weak_ptr
一起用于解决循环引用问题。 - 有一定开销:引用计数可能引入一些额外的开销。
std::weak_ptr
std::weak_ptr
也是C++11引入的,用于解决std::shared_ptr
可能导致的循环引用问题。std::weak_ptr
允许你观察std::shared_ptr
管理的对象,但不拥有它。
c++
#include <memory>
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr = sharedPtr; // 弱引用
主要特点:
- 弱引用:不拥有对象,仅用于观察
std::shared_ptr
。 - 避免循环引用:可以用于打破
std::shared_ptr
之间的循环引用,以防止内存泄漏。
使用推荐
通常推荐使用std::unique_ptr
来管理独占所有权的资源,使用std::shared_ptr
和std::weak_ptr
来管理共享所有权的资源和解决循环引用问题。避免使用已弃用的std::auto_ptr
。
shared_str -- week_ptr
weak_ptr这个智能指针是用来辅助shared_ptr工作的
在上面智能指针的介绍中,shared_ptr 是共享所有权智能指针,它的核心点在于共享,存在引用计数逻辑,在下面这种场景中,可能导致循环引用问题
c++
#include <memory>
class A;
class B;
class A {
public:
// 使用共享所有权智能指针
std::shared_ptr<B> b_ptr;
A() {
std::cout << "A constructor" << std::endl;
}
~A() {
std::cout << "A destructor" << std::endl;
}
};
class B {
public:
std::shared_ptr<A> a_ptr;
B() {
std::cout << "B constructor" << std::endl;
}
~B() {
std::cout << "B destructor" << std::endl;
}
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
// 形成循环引用
a->b_ptr = b;
b->a_ptr = a;
return 0;
}
在上面的示例中,类 A
和 B
分别包含一个 std::shared_ptr
,它们相互引用对方。当 a
和 b
的引用计数都不再为零时,它们的析构函数永远不会被调用,导致内存泄漏。
为了解决这个问题,可以使用 std::weak_ptr
来打破循环引用。std::weak_ptr
允许你观察 std::shared_ptr
管理的对象,但不拥有它,因此不会增加引用计数
以下是使用 std::weak_ptr
解决循环引用问题的代码
c++
#include <memory>
#include <iostream>
class A;
class B;
class A {
public:
// 使用 weak_ptr 代替 shared_ptr
std::weak_ptr<B> b_weak_ptr;
A() {
std::cout << "A constructor" << std::endl;
}
~A() {
std::cout << "A destructor" << std::endl;
}
};
class B {
public:
// 使用 weak_ptr 代替 shared_ptr
std::weak_ptr<A> a_weak_ptr;
B() {
std::cout << "B constructor" << std::endl;
}
~B() {
std::cout << "B destructor" << std::endl;
}
};
int main() {
// 创建 shared_ptr
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
// 使用 weak_ptr
a->b_weak_ptr = b;
b->a_weak_ptr = a;
// 当 a 和 b 的引用计数为零时,它们的析构函数会被调用,不会出现内存泄漏
return 0;
}
std::weak_ptr
用于替代了 std::shared_ptr
,并且在 main
函数的末尾显式地释放了 a
和 b
的 shared_ptr
,使它们的引用计数减少
对于 Java语言型选手,这个疑虑要消除
std::shared_ptr
和Java中的GC Roots(垃圾收集根节点)确实有一些相似之处,当看到引用计数这四个字时,肯定会想到GC roots 的算法。
我们来对比一下:
相似之处:
- 引用计数 :
std::shared_ptr
和GC Roots 都与引用计数相关。std::shared_ptr
使用引用计数来跟踪有多少个智能指针实例引用了同一对象。GC Roots 也类似,它们是根节点,跟踪哪些对象被引用,哪些不再被引用。 - 生命周期管理 :
std::shared_ptr
和GC Roots 都用于管理对象的生命周期。std::shared_ptr
通过引用计数来确保对象在没有引用时被释放,GC Roots 确保被引用的对象不会被垃圾收集器回收。 - 循环引用问题 :
std::shared_ptr
和GC Roots 都可以解决循环引用问题。在std::shared_ptr
中,使用std::weak_ptr
来打破循环引用,GC Roots 可以识别不再可达的对象并释放它们。
不同之处
- 语言和环境 :
std::shared_ptr
是C++中的一个智能指针,用于管理动态分配的内存。GC Roots 是Java虚拟机中的概念,用于垃圾回收。它们存在于不同的编程语言和执行环境中。 - 垃圾收集方式 :GC Roots 通常与垃圾回收算法(如标记-清除、分代垃圾回收)一起使用,以识别和收集不再可达的对象。而
std::shared_ptr
使用引用计数来跟踪对象的引用情况,不需要像垃圾回收器那样扫描整个对象图。 - 性能和开销 :由于不同的垃圾回收算法和环境要求,GC Roots 可能会引入一些性能开销,而
std::shared_ptr
的开销通常较小。
总结
在C++编程中,智能指针是一种强大的工具,可用于管理内存和资源,确保安全和高效的资源管理。std::shared_ptr
和std::unique_ptr
是现代C++中常用的智能指针类型,分别用于共享和独占资源所有权,在以后的开发中可以放心大胆的使用。