C++中的智能指针

时间:2020-02-23 14:30:04  来源:igfitidea点击:

在本文中,我们将介绍如何在C++中使用智能指针。

智能指针是实际指针(也称为原始指针)的抽象接口,但具有自动管理资源和释放内存的其他好处。

Smart Ptr示例

智能指针的设计逻辑是将其实现为类。

这是因为类具有析构函数,当对象位于其作用域的末尾时,它将自动触发。
智能指针使用析构函数自动释放资源。

您无需显式释放智能指针,因此它们是"智能"指针,因为它们知道何时需要释放资源。

通过示例,让我们更多地了解C++中的这些智能指针类型。

在C++中使用智能指针

这些指针在标准C++库的std名称空间中定义。
要包括其中一些指针,我们还需要 <memory>头文件。

有三种常用的智能指针,称为:

  • unique_ptr
  • shared_ptr
  • weak_ptr

让我们一一介绍。

unique_ptr – C++中的智能指针

这是一个智能指针,将只允许该指针的一个所有者。
也就是说,unique_ptr最多只能包含一个指向单个内存位置的原始指针("唯一"指针)。

除非您正在使用多个线程,或者需要多个所有者使用同一原始指针,否则请将其保留为默认选择。

要使用unique_ptr创建智能指针,有两种方法可以实现:

情况1:从对象创建唯一的智能指针

如果您有一个对象,并且想要在C++中创建指向该对象的智能指针,请使用以下语法:

std::unique_ptr<myType> my_ptr(new MyType());

上面的行创建了一个类型为MyTyp的新原始指针。
我们通过使用std :: unique_ptr <myType>来创建智能指针。

现在,您可以使用智能指针句柄,使用->运算符在对象上调用方法。

//Call a method on the object
my_ptr->ObjectMethod();

您还可以使用以下方法释放my_ptr拥有的内存:

my_ptr.reset();

我们使用.运算符,因为我们将其直接应用于智能指针。

情况2:构造指向存储位置的新的唯一指针

如果要构造一个新的唯一指针,只需使用make_unique()函数。

std::unique_ptr<std::string> = std::make_unique<std::string>("Hello from JournalDdev");

这将创建指向字符串" Hello from theitroad"的唯一指针。

其他操作保持不变。

移动唯一的指针

由于unique_ptr对于任何内存位置都是唯一的,因此我们不能具有指向多个对象的唯一指针。
因此,我们只能移动unique_ptr。

std::unique_ptr<int> unique_ptr_1(new int(1000));
//Move the raw pointer from unique_ptr_1 TO unique_ptr_2
std::unique_ptr<int> unique_ptr_2 = std::move(unique_ptr_1);

//Now, the data is pointed to by unique_ptr_2
unique_ptr_2->doSomething();

现在,让我们看一个示例,了解整个过程:

#include <iostream>
#include <memory>

class MyClass{
  public:
      int a;
      MyClass() { std::cout << "Default Constructor\n"; a = 10; }
      MyClass(int value = 100) { std::cout << "Parametrized Constructor\n"; a = value; }
};

int main() {
  //Object of MyClass
  MyClass my_obj(250);

  //Construct the pointer using make_unique()
  std::unique_ptr<MyClass> my_ptr_1 = std::make_unique<MyClass>(my_obj);
  //Create a new object and get the unique_ptr to it
  std::unique_ptr<MyClass> my_ptr_2(new MyClass(500));
  
  std::cout << "Value of a (my_ptr_1): " << my_ptr_1->a << std::endl;
  std::cout << "Value of a (my_ptr_2): " << my_ptr_2->a << std::endl;

  //Both the pointers will automatically get destroyed, since the objects are at the end of their scopes
  //But if we want to explicitly free the raw pointer, we can use unique_ptr.reset()
  std::cout << "Freeing the pointers...\n";
  my_ptr_1.reset();
  my_ptr_2.reset();
  return 0;
}

输出

Parametrized Constructor
Parametrized Constructor
Value of a (my_ptr_1): 250
Value of a (my_ptr_2): 500
Freeing the pointers...

shared_ptr

如果您想与多个所有者打交道,shared_ptr是C++中智能指针的另一种选择。
这还将维护指向该对象的所有指针的引用计数。

unique_ptr类似,语法几乎相同,不同之处在于,您现在返回一个共享指针。
现在还允许您将多个对象传递给调用,以便shared_ptr指向所有这些对象。

要构造一个新的共享指针,请使用make_shared()函数。

//my_ptr points to two objects
std::unique_ptr<myType> my_ptr(new MyType(1), new MyType(2));

//A shared pointer to 2 strings
std::unique_ptr<string> my_str_ptr = std::make_shared<string>("Hello", "How are you?");

不需要使用std :: move,因为共享指针可以指向多个位置。

要获取任何/所有共享指针的当前引用计数,请使用" my_shared_ptr.use_count()"。
当我们创建或者释放指向公共对象的共享指针时,它将自动更新。

我们将看一个有关shared_ptr的示例。

#include <iostream>
#include <memory>

class MyClass{
  public:
      int a;
      MyClass() { std::cout << "Default Constructor\n"; a = 10; }
      MyClass(int value = 100) { std::cout << "Parametrized Constructor\n"; a = value; }
};

int main() {
  //Objects of MyClass
  MyClass my_obj(250);

  //Construct a pointer using make_shared
  std::shared_ptr<MyClass> my_ptr_1 = std::make_shared<MyClass>(my_obj);
  std::cout << "Current Reference Count of my_ptr_1 = " << my_ptr_1.use_count() << std::endl;
  //And another shared pointer to the same location!
  std::shared_ptr<MyClass> my_ptr_2 = my_ptr_1;

  std::cout << "Current Reference Count of my_ptr_1 = " << my_ptr_1.use_count() << std::endl;
  std::cout << "Current Reference Count of my_ptr_2 = " << my_ptr_2.use_count() << std::endl;

  std::cout << "Value of a (from dereferencing my_ptr_1): " << my_ptr_1->a << std::endl;
  std::cout << "Value of a (from dereferencing my_ptr_2): " << my_ptr_2->a << std::endl;

  //Both the pointers will automatically get destroyed, since the objects are at the end of their scopes
  //But if we want to explicitly free the raw pointer, we can use unique_ptr.reset()
  std::cout << "Freeing the pointers...\n";
  my_ptr_1.reset();
  std::cout << "Freed my_ptr_1\n";
  std::cout << "Current Reference Count of my_ptr_1 = " << my_ptr_1.use_count() << std::endl;
  std::cout << "Current Reference Count of my_ptr_2 = " << my_ptr_2.use_count() << std::endl;
  my_ptr_2.reset();
  std::cout << "Freed my_ptr_2\n";
  std::cout << "Current Reference Count of my_ptr_1 = " << my_ptr_1.use_count() << std::endl;
  std::cout << "Current Reference Count of my_ptr_2 = " << my_ptr_2.use_count() << std::endl;
  return 0;
}

输出

Parametrized Constructor
Current Reference Count of my_ptr_1 = 1
Current Reference Count of my_ptr_1 = 2
Current Reference Count of my_ptr_2 = 2
Value of a (from dereferencing my_ptr_1): 250
Value of a (from dereferencing my_ptr_2): 250
Freeing the pointers...
Freed my_ptr_1
Current Reference Count of my_ptr_1 = 0
Current Reference Count of my_ptr_2 = 1
Freed my_ptr_2
Current Reference Count of my_ptr_1 = 0
Current Reference Count of my_ptr_2 = 0

如您所见,两个指针都指向同一对象。
在释放其中任何一个之后,再次更新参考计数以反映更改。

weak_ptr

weak_ptr是C++中的一个智能指针,类似于shared_ptr,但是它不维护引用计数。
在一个类的对象可以指向另一类,反之亦然的情况下,这很有用。

考虑以下情况,其中有两个类" A"和" B",另一个类具有shared_ptr成员(即A的对象可以指向B的对象,反之亦然)。
现在,当您创建两个对象" A"和" B"时,使用" shared_ptr"成员使它们指向彼此,现在会发生什么?

#include <iostream>
#include <memory>

//Forward declare class B
class B;

class A{
  public:
      int a;
      std::shared_ptr ptr;
      A(int value = 200) { a = value; }
      ~A() {std::cout << "Destructor for A\n"; }
};

class B{
  public:
      int a;
      std::shared_ptr<A> ptr;
      B(int value = 200) { a = value; }
      ~B() {std::cout << "Destructor for B\n"; }
};

int main() {
  std::shared_ptr<A> ptr_a = std::make_shared<A>(750);
  std::shared_ptr ptr_b = std::make_shared(750);
  
  //Make ptr_a point to ptr_b
  ptr_a->ptr = ptr_b;
  //And make ptr_b point to ptr_a
  ptr_b->ptr = ptr_a;

  return 0;
}

输出


是的,即使我们希望析构函数可以为我们清理,我们也不会得到任何输出!怎么了?!!

这是循环引用的示例,其中的指针相互指向。
由于这些指针是shared_ptr,因此它们的引用计数也为2。

循环参考共享Ptr

当" ptr_a"对象的析构函数试图清理时,它会发现" ptr_b"指向" ptr_a",因此无法简单地将其删除。
同样,ptr_b的对象的析构函数也无法清除,导致没有输出!

为了消除这个问题,我们可以在类中使用weak_ptr智能指针。
由于弱指针不会增加引用计数,因此我们可以将两个指针之一作为弱指针,例如,将" ptr_b"更改为" ptr_a"。

由于弱指针不拥有数据,因此我们现在可以清除共享指针" a",然后还可以清除" b"!

因此,我们可以使B类的ptr成员成为weak_ptr。
(您也可以为A做,但我们会为B做)

解决依赖性

现在,我们对代码段进行更改以使其正常工作。

#include <iostream>
#include <memory>

//Forward declare class B
class B;

class A{
  public:
      int a;
      std::shared_ptr ptr;
      A(int value = 200) { a = value; }
      ~A() {std::cout << "Destructor for A\n"; }
};

class B{
  public:
      int a;
      std::weak_ptr<A> ptr;
      B(int value = 200) { a = value; }
      ~B() {std::cout << "Destructor for B\n"; }
};

int main() {
  std::shared_ptr<A> ptr_a = std::make_shared<A>(750);
  std::shared_ptr ptr_b = std::make_shared(750);
  
  //Make ptr_a point to ptr_b
  ptr_a->ptr = ptr_b;
  //And make ptr_b point to ptr_a
  //Since ptr_b->ptr is a weak pointer, we don't have the problem now!
  ptr_b->ptr = ptr_a;

  return 0;
}

输出

Destructor for A
Destructor for B

现在,我们将B类的智能指针的类型更改为weak_ptr
因此,现在不再有循环依赖项,我们终于解决了这个问题!

通用编程准则

总结一下本文,我将为您提供一小部分准则,以供在C++中使用SMART指针时遵循。

  • 尽可能尝试使用智能指针。
    在大多数情况下,如果您不打算与多个指针/线程共享内存位置,请使用" unique_ptr"。

  • 否则,在与多个所有者打交道时,请使用引用计数的" shared_ptr"。

  • 如果要检查对象,但不要求对象本身存在,请使用" weak_ptr"。
    该指针不会增加引用计数,并且适合此类任务。

  • 每当您直接使用原始指针时,请尝试确保仅将其包含在非常少量的代码中或者必须绝对使用的地方,以减少使用智能指针的开销。