CPP:智能指针

文章目录[x]
  1. 1:智能指针
  2. 1.1:创建
  3. 1.2:智能指针和传统指针的对比
  4. 1.3:作为参数传递
  5. 2:智能指针类型
  6. 2.1:unique_ptr
  7. 2.2:shared_ptr
  8. 2.3:weak_ptr

指针是一个变量,用于存储对象的内存地址。指针在CC ++中广泛用于以下三个主要目的:

  • 函数传递
  • 在堆上分配对象
  • 遍历数组或其他数据结构中的元素

C风格的编程中,都是使用原始指针。但是,原始指针是许多严重的编程错误的根源。因此,强烈建议不要使用它们,除非它们提供了显著的性能好处。C ++11提供了用于分配对象的智能指针,用于遍历数据结构的迭代器以及用于传递函数的lambda表达式。通过使用智能指针而不是原始指针,可以使程序更安全,更易于调试,更易于理解和维护。

智能指针是在C++11中被引入,std<memory>头文件的名称空间中定义。主要分为unique_ptrshared_ptrweak_ptr

智能指针

创建

如下是在C++11中定义一个unique_ptr智能指针:

std::unique_ptr<Person> person(new Person);

C++14中,还可以使用std::make_unique函数来创建:

auto ptr = std::make_unique<Person>();

智能指针和传统指针的对比

void UseRawPointer()
{
// 使用原始指针-不推荐
Person* p = new Person;

 

 

// Use p...

p->doSomething();

 

// Don't forget to delete!
delete p;
}

 

void UseSmartPointer()
{
// 在堆栈上声明一个原始指针,并将其传递给智能指针
std::unique_ptr<Person> person(new Person);

// Use person...

person->doSomething();

//通过*操作取原始对象
auto obj =*person;
obj.age = 18;

//如果需要在函数执行完前释放资源则可以调用reset方法
//person.reset();

//...

}//person将在此处自动delete
如上面的代码所示,智能指针是在堆栈上声明的,并使用指向堆分配对象的原始指针进行初始化。智能指针初始化后,便拥有原始指针。这意味着智能指针负责删除原始指针指定的内存。智能指针析构函数包含对delete的调用,并且由于智能指针是在堆栈上声明的,因此即使智能异常超出堆栈范围,也会在智能指针超出范围时调用其析构函数。
还可以通过使用熟悉的指针运算符->来访问封装的指针,*智能指针类将重载该运算符以返回封装的原始对象。
C ++智能指针习惯用法类似于C#等语言的对象创建:创建对象,然后让系统负责在正确的时间删除它。不同之处在于,C++中没有单独的垃圾收集器在后台运行。内存通过标准C ++范围规则进行管理,因此运行时更快,更高效。

作为参数传递

应该始终在单独的代码行上创建智能指针,而不要在参数列表中创建智能指针,以免由于某些参数列表分配规则而导致细微的资源泄漏:

class Man{
public:
void doSomething(){}
void init(){}
};

 

void process(Man &man){
man.doSomething();
}

int main() {
std::unique_ptr<Man> ptr(new Man);
ptr->init();
process(*ptr);
return 0;
}
注意:不要在智能指针本身上使用new和malloc。
智能指针被设计为在内存和性能上都尽可能高效。例如,唯一的数据成员unique_ptr是封装的指针。这意味着它unique_ptr与该指针的大小完全相同,为四个字节或八个字节。通过使用智能指针重载*->运算符来访问封装的指针,并不比直接访问原始指针慢得多。
智能指针具有自己的成员函数,可以使用.表示法对其进行访问。例如,如果需要在函数执行完前释放资源则可以调用reset方法。

智能指针类型

unique_ptr

unique_ptr不会共享它的指针,也不能将其复制到另一个unique_ptr。可以通过值传递给函数。unique_ptr只能移动。这意味着内存资源的所有权会转移到另一个unique_ptr,原始的unique_ptr不再拥有它,所以建议将一个对象限制为一个unique_ptr,因为多重所有权会增加程序逻辑的复杂性。
下图说明了两个unique_ptr实例之间的所有权转移。
unique_ptrC ++标准库的标头memory中定义。它与原始指针一样高效。move函数消除了复制操作。

如何创建unique_ptr实例并在函数之间传递实例

class Person{

 

public:
int m_age;
std::string m_name;
Person(std::string& name,int age):m_age(age),m_name(name)
{

 

}
};

 

 

std::unique_ptr<Person> PersonFactory(int&& age,std::string&& name){

return std::make_unique<Person>(name,age);
}

 

 

int main() {
//通过std::make_unique创建其Person对象的智能指针
std::string name = "melrose";
auto p1 = std::make_unique<Person>(name,25);

 

// 将原始指针从一个unique_ptr移到另一个
auto p2 = std::move(p1);
//如果再通过p1去获取原始指针会报错
printf("p2: %s %d\n",p2->m_name.c_str(),p2->m_age);

 

//隐式移动操作到存储结果的变量中
auto p3 = PersonFactory(18,"Jhon");

 

return 0;
}

在数组和vector中使用unique_ptr

//在vector中使用
void PersonVector(){
std::vector<std::unique_ptr<Person>> persons;
persons.push_back(std::make_unique<Person>("t1",22));
persons.push_back(std::make_unique<Person>("t2",23));
persons.push_back(std::make_unique<Person>("t3",24));

 

for (const auto &p:persons) {
printf("%s %d\n",p->m_name.c_str(),p->m_age);
}
}

 

//数组中使用unique_ptr
void Array(){
// 创建一个由5个整数组成的数组的unique_ptr。
auto p = std::make_unique<int[]>(5);

 

// 初始化数组。
for (int i = 0; i < 5; ++i)
{
p[i] = i;
}
}

shared_ptr

shared_ptr类型也是C ++11标准库中出来的智能指针,该指针是为可能需要多个所有者来管理内存中对象生命周期的方案所设计的。初始化该智能指针之后,shared_ptr可以被复制,在函数参数中按值传递它,并可将其分配给其他shared_ptr实例。所有实例都指向同一个对象,并共享对一个控制块的访问权,每当shared_ptr添加新的,超出范围的或重置时,该控制块都会递增和递减引用计数。当引用计数达到0时,控制块将删除内存资源及其本身。

下图显示了shared_ptr指向一个内存位置的多个实例:

应该在第一次创建内存资源时就使用make_shared函数来创建shared_ptr。它使用相同的控制块和资源分配内存,从而减少了构造开销。如果不使用make_shared,则必须使用显式new表达式创建对象,然后再将其传递给shared_ptr构造函数。以下示例显示了shared_ptr与新对象一起声明和初始化的各种方法:

//使用std::make_shared函数来创建shared_ptr
auto sp1 = std::make_shared<Person>();

 

// 通过构造函数创建
std::shared_ptr<Person> sp2(new Person);

 

//等效于:std::shared_ptr<Person> sp5;
std::shared_ptr<Person> sp5(nullptr);
sp5 = std::make_shared<Person>();

下面的示例演示如何声明和初始化shared_ptr实例,这些实例具有已由另一个对象分配的对象的共享所有权shared_ptr创建:

auto sp1 = std::make_shared<Person>();
sp1->age = 25;

 

//用复制构造函数初始化, 引用计数增加
auto sp2(sp1);
//通过分配进行初始化, 引用计数增加
auto sp3 = sp1;

 

//用另一个shared_ptr初始化,sp1和sp2交换指针以及引用计数
sp1.swap(sp2);

//使用use_count方法可以查看引用的数量
printf("use count:%d",sp1.use_count());

可以通过以下方式将shared_ptr传递给另一个函数:

  • 传递shared_ptr按值传递。这将调用复制构造函数,增加引用计数,并使被调用方成为所有者。此操作的开销很少,具体取决于shared_ptr要传递的对象数。当调用方和被调用方之间的隐式或显式代码约定要求被调用方为所有者时,可以使用此选项。
  • 传递shared_ptr引用或const引用。在这种情况下,引用计数不会增加,只要被调用方没有超出范围,被调用就可以访问该指针。或者,被调用方可以决定shared_ptr基于引用创建一个,并成为共享所有者。当调用者不了解被调用者时,或者必须传递shared_ptr并且出于性能原因而希望避免复制操作时,可以使用此选项。
  • 将基础指针或引用传递给基础对象。这使被调用者可以使用该对象,但不能使其共享所有权或延长生存期。如果被调用方通过原始指针创建了shared_ptr,则新对象shared_ptr将独立于原始对象,并且不会控制原始资源。当调用者和被调用者之间的明确指定调用者保留shared_ptr生命周期的所有权时,可以使用此选项。
  • 在决定如何传递时shared_ptr,需确定被调用方是否必须共享基础资源的所有权。所有者是一个对象或函数,可以根据需要保持其生存时间。如果调用者必须保证被调用者可以将指针的寿命延长到其(函数的)寿命之外,请使用第一个选项。如果不在乎被调用方是否延长生存期,请通过引用传递并让被调用方复制或不复制。
  • 如果必须授予辅助函数访问基础指针的权限,并且知道该辅助函数将仅使用该指针并在调用函数返回之前返回,则该函数不必共享基础指针的所有权。它只需要在调用者的生存期内访问指针shared_ptr。在这种情况下,可以按引用安全地传递shared_ptr,或将原始指针或引用传递给基础对象。

weak_ptr

有时,对象必须存储一种访问shared_ptr的基础对象的方法,而不需要导致引用计数增加。通常,当shared_ptr实例之间有循环引用时,则会发生这种情况。
最好的解决方案是尽可能避免共享指针的所有权。但是,如果必须具有shared_ptr实例的共享所有权,请避免在它们之间使用循环引用。如果不可避免要使用循环引用,可以使用weak_ptr为一个或多个所有者提供对另一个所有者的弱引用shared_ptr。通过使用weak_ptr,可以创建一个shared_ptr与现有的相关实例集连接的,但前提是基础内存资源仍然有效。一个weak_ptr本身不参与引用计数,因此,它不能阻止的引用计数从去到零。但是,可以使用weak_ptr尝试获取新的shared_ptr用它初始化。如果内存已被删除,则weak_ptr bool运算符将返回false。如果内存仍然有效,则新的共享指针将增加引用计数,并确保只要shared_ptr变量在范围内,内存才有效。
std::vector<std::weak_ptr<Person>> persons;

auto p = std::make_shared<Person>();

persons.push_back(std::weak_ptr<Person>(p));
点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00