智能指针
独占指针: unique_ptr
介绍
在任何给定时刻,只能有一个指针管理内存
该类型指针不能copy,只能Move
使用 unique_ptr
可以避免内存泄漏和手动释放资源的问题,它会在不再需要时自动释放所管理的对象。当 unique_ptr
被销毁时,它会自动调用 delete
来释放对象的内存。
unique_ptr有三种常用的创建方式
- 使用
new
关键字创建:可以使用new
关键字直接创建一个对象,并将其传递给unique_ptr
的构造函数。 - 使用
std::make_unique
函数创建:C++14 引入了std::make_unique
函数,可以更方便地创建unique_ptr
对象。 - 使用
reset
函数创建:可以先创建一个空的unique_ptr
,然后使用reset
函数重新指定对象。
这些创建方式都可以用来创建
unique_ptr
对象,并将其管理的资源初始化为指定的值。需要注意的是,在使用new
创建对象时,需要手动释放资源,而使用make_unique
函数或reset
函数创建的unique_ptr
在作用域结束时会自动释放资源。
3种创建方式
#include "cat.h"
#include <iostream>
#include <memory>
using namespace std;
int main(int argc,char* argv[]){
std::unique_ptr<Cat> u_c_p2{new Cat("yz")};
u_c_p2->cat_info();
return 0;
}
这样会自动释放内存,防止内存泄漏和悬空指针问题
#include "cat.h"
#include <iostream>
#include <memory>
using namespace std;
int main(int argc,char* argv[]){
std::unique_ptr<Cat> u_c_p2 = make_unique<Cat>();
u_c_p2->cat_info();
u_c_p2->set_cat_name("meow");
u_c_p2->cat_info();
return 0;
}
#include "cat.h"
#include <iostream>
#include <memory>
using namespace std;
int main(int argc,char* argv[]){
std::unique_ptr<Cat> u_c_p2;
u_c_p2.reset(new Cat("yz"));
u_c_p2->cat_info();
return 0;
}
不安全的raw pointer
cat.h
#ifndef CAT_H
#define CAT_H
#include <string>
#include <iostream>
class Cat
{
public:
Cat(std::string name);
Cat() = default;
~Cat();
void cat_info() const{
std::cout << "cat info name:" << name << std::endl;
}
std::string get_name() const {
return name;
}
void set_cat_name(const std::string& name){
this->name = name;
}
private:
std::string name{"Mini"};
};
#endif
cat.cpp
#include "cat.h"
Cat::Cat(std::string _name) : name(_name)
{
std::cout << "Constructor of Cat:" << name << std::endl;
}
Cat::~Cat() {
std::cout << "Destructor of Cat " << name << std::endl;
}
main.cpp
#include "cat.h"
#include <iostream>
#include <memory>
using namespace std;
int main(int argc,char* argv[]){
Cat *cat = new Cat("yy");
cat->cat_info();
delete cat; //raw pointer必须使用Delete进行删除,如果没有delete,是不安全的
return 0;
}
makefile
# 编译器
CXX := g++
# 编译选项
CXXFLAGS := -std=c++11 -Wall
# 目标文件列表
OBJS := output/cat.o output/main.o
# 默认目标
all: create_dir program
# 生成可执行文件
program: $(OBJS)
$(CXX) $(CXXFLAGS) $^ -o $@
# 编译源文件
output/%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
# 创建 output 目录
create_dir:
mkdir -p output
# 清理生成的目标文件和可执行文件
clean:
rm -rf output program
获取unique_ptr指向的对象的地址
要获取 unique_ptr
指向的对象的地址,可以使用 get()
成员函数。get()
返回一个指向被 unique_ptr
管理的对象的原始指针。
要调用 unique_ptr
管理的对象的成员函数,可以使用箭头操作符 ->
。箭头操作符用于访问指针所指向对象的成员。
#include "cat.h"
#include <iostream>
#include <memory>
using namespace std;
int main(int argc,char* argv[]){
std::unique_ptr<Cat> u_c_p2{new Cat("meow")};
std::cout << u_c_p2.get() << std::endl;
return 0;
}
unique_ptr与函数调用
#include "cat.h"
#include <iostream>
#include <memory>
void do_with_cat_pass_value(std::unique_ptr<Cat> c) {
c->cat_info();
}
void do_with_cat_pass_reference(const std::unique_ptr<Cat> &c) {
c->cat_info();
}
#include <memory>
int main(int argc,char* argv[]){
//pass value
std::unique_ptr<Cat> c1 = std::make_unique<Cat>("mimi");
do_with_cat_pass_value(std::move(c1));
do_with_cat_pass_value(std::make_unique<Cat>("meowmeow"));
std::unique_ptr<Cat> c2 = std::make_unique<Cat>("jk");
do_with_cat_pass_reference(c2);
c2->cat_info();
c2.reset();
std::cout << "c2 address: " << c2.get() << std::endl;
return 0;
/*
Constructor of Cat:mimi
cat info name:mimi
Destructor of Cat mimi
Constructor of Cat:meowmeow
cat info name:meowmeow
Destructor of Cat meowmeow
Constructor of Cat:jk
cat info name:jk
cat info name:jk
Destructor of Cat jk
c2 address: 0
*/
}
对象所有权是指对一个对象的控制权和责任。在 C++ 中,通过使用智能指针(如 std::unique_ptr
和 std::shared_ptr
)来管理动态分配的对象,可以实现对象所有权的转移和共享。
在 C++ 中,通过动态内存分配(使用 new
运算符)创建的对象由创建者拥有所有权。这意味着创建者有责任在适当的时候释放该对象所占用的内存,以避免内存泄漏。
然而,当对象的所有权需要转移到另一个对象时,可以使用移动语义来实现。移动语义允许将资源(如内存)从一个对象转移到另一个对象,而无需进行深拷贝或复制。通过移动语义,可以避免不必要的数据复制和资源管理开销,并提高程序性能。
std::unique_ptr
和 std::shared_ptr
是智能指针,它们通过使用所有权概念来管理对象的生命周期。std::unique_ptr
表示独占所有权,即一个对象只能由一个 std::unique_ptr
持有,当 std::unique_ptr
被销毁时,它会自动释放所管理的对象。std::shared_ptr
表示共享所有权,即多个 std::shared_ptr
可以同时持有一个对象,并在最后一个 std::shared_ptr
被销毁时释放对象。
通过正确管理对象的所有权,可以确保对象的生命周期正确且高效地管理,避免资源泄漏和悬空指针等问题。
std::move是一个函数模板,位于
头文件中,用于将对象转移到另一个对象。在你的代码中,你使用
std::move(c1)来将
c1的所有权转移到
do_with_cat_pass_value函数中的
c。但是,在
main函数中,你又尝试访问
c1,这是不正确的,因为它在转移所有权后不再拥有
Cat` 对象。
计数指针(共享指针) shared_ptr
std::shared_ptr
是 C++ 中的智能指针,用于管理动态分配的对象。它提供了自动的内存管理和资源释放,可以帮助避免内存泄漏和悬挂指针的问题。std::shared_ptr
使用引用计数的方式来跟踪指针的所有权,当引用计数为零时,它会自动释放所管理的对象。
#include <iostream>
#include <memory>
struct MyObject {
int value;
MyObject(int val) : value(val) {
std::cout << "Constructing MyObject with value: " << value << std::endl;
}
~MyObject() {
std::cout << "Destructing MyObject with value: " << value << std::endl;
}
};
int main() {
std::shared_ptr<MyObject> ptr = std::make_shared<MyObject>(42);
std::cout << "Accessing MyObject value: " << ptr->value << std::endl;
// 输出: Constructing MyObject with value: 42
// 输出: Accessing MyObject value: 42
{
std::shared_ptr<MyObject> ptr2 = ptr;
std::cout << "Accessing MyObject value through ptr2: " << ptr2->value << std::endl;
// 输出: Accessing MyObject value through ptr2: 42
}
std::cout << "MyObject value: " << ptr->value << std::endl;
// 输出: MyObject value: 42
// ptr2 超出作用域,但不会触发析构函数调用
ptr.reset(); // 手动重置 shared_ptr,释放对象
std::cout << "After reset()" << std::endl;
// 输出: Destructing MyObject with value: 42
// 输出: After reset()
return 0;
}
在这个例子中,我们创建了一个 MyObject
类,它具有一个整数成员变量 value
。在 main
函数中,我们使用 std::make_shared
创建了一个 shared_ptr
,指向一个 MyObject
对象,并将值设置为 42。然后,我们通过 ptr
访问对象的值,并输出结果。
接下来,我们创建了另一个 shared_ptr
,命名为 ptr2
,并将其指向 ptr
所指向的对象。我们通过 ptr2
访问对象的值,并输出结果。
在 ptr2
超出作用域后,引用计数减少,但不会触发析构函数的调用,因为 ptr
仍然指向对象。
最后,我们通过调用 ptr.reset()
手动重置了 ptr
,释放了对象。此时,引用计数变为零,触发了对象的析构函数的调用。
输出结果显示了对象的构造和析构过程,以及访问对象值的过程。
这个例子展示了 shared_ptr
的自动资源管理功能。它确保对象在不再被引用时被释放,避免了内存泄漏,并在对象被释放时自动调用析构函数。
weak_ptr 弱引用智能指针
C++ 提供了 weak_ptr,它是一种弱引用智能指针。weak_ptr 允许我们获取对 shared_ptr 管理的对象的临时访问权,但不会增加引用计数,也不负责对象的生命周期管理。
下面是 weak_ptr
的一些重要特点和用法:
-
创建
weak_ptr
:可以通过将shared_ptr
赋值给weak_ptr
来创建它。例如:std::shared_ptr<int> sharedPtr = std::make_shared<int>(42); std::weak_ptr<int> weakPtr = sharedPtr;
-
使用
lock()
获取shared_ptr
:weak_ptr
提供了一个lock()
成员函数,用于获取一个有效的shared_ptr
。如果原始的shared_ptr
仍然存在,lock()
将返回一个指向相同对象的有效shared_ptr
;否则,它将返回一个空的shared_ptr
。例如:std::shared_ptr<int> sharedPtr = std::make_shared<int>(42); std::weak_ptr<int> weakPtr = sharedPtr; std::shared_ptr<int> lockedPtr = weakPtr.lock(); if (lockedPtr) { // 使用 lockedPtr 访问对象 } else { // weakPtr 对应的 shared_ptr 已经失效 }
通过使用
lock()
,我们可以确保只在shared_ptr
仍然有效时访问对象,避免访问已经被释放的对象。 -
检查
expired()
:weak_ptr
还提供了expired()
成员函数,用于检查原始的shared_ptr
是否已经失效。如果shared_ptr
仍然有效,expired()
返回false
;否则,返回true
。例如:std::shared_ptr<int> sharedPtr = std::make_shared<int>(42); std::weak_ptr<int> weakPtr = sharedPtr; if (!weakPtr.expired()) { // sharedPtr 仍然有效 } else { // sharedPtr 已经失效 }
通过使用
expired()
,我们可以在不访问对象的情况下检查shared_ptr
是否有效。
使用 weak_ptr
可以避免循环引用导致的内存泄漏问题,并且可以安全地访问 shared_ptr
管理的对象。但需要注意的是,由于 weak_ptr
不会增加引用计数,所以在使用 weak_ptr
指向对象时,需要先通过 lock()
获取有效的 shared_ptr
,以确保对象仍然存在。