掘根宝典之C++智能指针模板类(auto_ptr,unique_ptr,shared_ptr,weak_ptr)

什么是智能指针

说白了,智能指针就是类似于指针的类对象,但是功能比指针多。

智能指针是一种在程序中管理动态分配的内存的工具。智能指针提供了一种机制来自动分配和释放内存,从而减少内存泄漏和悬挂指针的风险。智能指针通过将内存的所有权转移到指针对象本身,可以在对象不再需要时自动释放内存。

智能指针通常会在构造函数中分配内存,并在析构函数中释放内存。此外,智能指针还提供了一些额外的功能,如拷贝构造函数和移动构造函数,以便能够正确地管理指针的所有权。

常见的智能指针模板类型有:std::shared_ptr、std::unique_ptr和std::weak_ptr。

  • std::shared_ptr是一种共享所有权的智能指针,多个shared_ptr对象可以指向同一块内存,并且在最后一个shared_ptr对象被释放时才会释放内存。
  • std::unique_ptr是一种独占所有权的智能指针,只能有一个unique_ptr对象指向一块内存,当unique_ptr对象被释放时,它所指向的内存也会被释放。
  • std::weak_ptr是一种弱引用的智能指针,它可以共享对象的所有权,但不会增加对象的引用计数。weak_ptr通常用于解决循环引用的问题。

通过使用智能指针,程序员可以更安全地管理内存,并避免一些常见的内存管理错误。

auto_ptr是C++98标准引入的智能指针,但在C++11标准中被弃用,并建议使用unique_ptr替代。

智能指针和普通指针的区别

我们以auto_ptr为例,来和普通指针进行比较,(因为所有智能指针在释放动态内存这里都是如此)

普通指针

void A()
{
int* a=new int;    //为a和一个int值分配存储空间,保存地址
*a=39;              //将值复制到动态内存中
return ;            //删除a,值被保存到动态内存中
}

智能指针模板类

void B()
{
auto_ptra(new int);//为a和一个int值分配存储空间,保存地址
*a=10;                  //将值复制到动态内存中
return;                 //删除a,a的析构函数释放动态内存
}

shared_ptr和unique_ptr的行为和auto_ptr的行为相同。

​智能指针的特性

注意:智能指针模板都位于std命名空间里

1.所有的智能指针类都有一个explicit构造函数,该构造函数以指针为参数

也就是说,C++不会自动将普通指针转换为智能指针(注意不是说不能转换哈)

#include
#include
using namespace std;
int main()
{
	int* b = new int(4);
	auto_ptrc=b;//这是不行的
	cout << *c << endl;
}

 但是我们换种方式就行了

#include
#include
using namespace std;
int main()
{
	int* b = new int(4);
	auto_ptrc(b);
	cout << *c << endl;//结果是4
}

这也就意味着我们也能将智能指针赋给相同类型的普通指针 

除了上面这个,如果a是一个智能指针对象,还可以对它进行解引用,用它来访问结构成员(->),将它赋给相同类型的常规指针。

注意了哈。我们要禁止将智能指针(是所有智能指针)指向非堆内存。因为智能指针会自动调用delete,这对于非堆内存来说是错误的。说白了,就是只能将智能指针指向动态开辟的内存。

我们看个例子

#include
#include
using namespace std;
int main()
{
	int a = 2;
	int* b = &a;
	auto_ptrc(b);//这是不行的
	cout << *c << endl;
}

有关智能指针的注意事项 

我们先看下面这个例子

auto_ptra(new int(3));
auto_ptrb;
b=a;

如果a,b是普通指针,则两个指针1将同时指向同一片内存。这是不能接受的,因为程序将试图删除同一个对象两次——一次是a过期的时候,另一次是b过期的时候。那我们怎么避免这种问题呢?

1.进行深复制——重载赋值运算符

2.建立所有权概念:对于特定的对象,只能有一个智能指针拥有它,这样只有拥有对象的智能指针的构造函数会删除该对象。然后让赋值操作转让所有权。这是用于auto_ptr和unique_ptr的策略。

3.创建智能更高的指针,跟踪引用特定对象的智能指针数。这被称为引用计数。例如,赋值时,计数将加1,而指针过期时,计数将减一。仅当最后一个指针过期时,才调用delete。这是shared_ptr的策略。

我们看一个例子

#include
#include
using namespace std;
int main()
{
	auto_ptra(new int(2));
	cout << *a << endl;     //结构是2
	auto_ptrb;
	b = a;                 //这是不行的
	cout << *a << endl;
    cout<< *b<

我们发现将a赋给b后,就不能对a解引用了。

这是因为auto_ptr奉行所有权原则——一个地址只能被一个智能指针指向。将a赋给b时,a放弃了那块地址的所有权,转交到了b上,这个时候我们可以对b解引用 

那如果我们将auto_ptr换成shared_ptr会怎么样呢?

我们看看

#include
#include
using namespace std;
int main()
{
	shared_ptra(new int(2));
	cout << *a << endl;     //结果是2
	shared_ptrb;
	b = a;
	cout << *a << endl;    //结果是2
	cout << *b << endl;    //结果是2
}

发现没有任何问题,因为shared_ptr遵循的不是所有权原则。所以同一块内存可以被多个shared_ptr指针指向

那unique_ptr呢?

#include
#include
using namespace std;
int main()
{
	unique_ptra(new int(2));
	cout << *a << endl;
	unique_ptrb;
	b = a;                //编译器发现这里错误
	cout << *a << endl;
	cout << *b << endl;
}

unique_ptr也是采用所有权模型。但是使用unique_ptr智能指针时,程序不会等到运行阶段崩溃,而在编译阶段因下面代码出现错误

b=a;

 auto_ptr和unique_ptr智能指针的区别

它们的主要区别在于所有权的转移和安全性。

1. 所有权的转移:
   - auto_ptr存在所有权的转移,即当一个auto_ptr对象给另一个对象赋值时,所有权会从原来的对象转移到新的对象上。这意味着原来的对象会变为空指针。这种转移可以导致潜在的问题,例如多个指针同时指向同一块内存,可能会导致内存泄漏或无效访问。
   - unique_ptr在所有权的转移方面更加严格和安全。它只允许一个unique_ptr指针指向某块内存,不能复制或赋值给其他unique_ptr对象。当unique_ptr对象被赋值给另一个unique_ptr对象时,所有权会完全转移到新的对象上,而原来的对象会变为空指针。

auto_ptra(new int(2));
auto_ptrb;
b=a;              //b接管2的所有权,a变为空指针
 

unique_ptrc(new int(2));
unique_ptrd;
d=c;             //unique_ptr禁止赋值给另一个

2. 安全性:
   - auto_ptr存在一些安全问题。例如,当auto_ptr对象被复制或赋值给其他对象时,原来的对象会变为空指针。这可能导致潜在的问题,例如多个指针同时指向同一块内存,可能会导致内存泄漏或无效访问。
   - unique_ptr则提供了更高的安全性。它禁止复制和赋值操作,确保只有一个unique_ptr指针指向某块内存。这降低了指针操作的潜在风险,并且在编译时会进行类型检查,提高了代码的安全性。

总的来说,unique_ptr比auto_ptr更加灵活和安全。它是C++11引入的新智能指针,推荐在现代C++代码中使用。而auto_ptr已经被废弃,不建议在新的代码中使用。

将一个unique_ptr对象赋给另一个unique_ptr对象的方法

 有人说啊,unique_ptr不是不能赋给另一个unique_ptr吗?

但实际上下面这两种情况是可以的

1.原unique_ptr是一个临时对象

#include
#include
using namespace std;

unique_ptr A()
{
	unique_ptra(new int(2));
	return a;
}

int main()
{
	unique_ptrb = A();
	cout << *b << endl;
}

A()返回一个临时对象unique_ptr,然后b接管了原本归返回的unique_ptr对象a,而a被销毁。这是允许的

2.使用std::move()函数来转移 

unique_ptra(new int(2));
unique_ptrb;
b=std::move(a);//这是可以的

智能指针里的new和delete

auto_ptr,shared_ptr均使用delete

unique_ptr使用delete[]和delete

所以使用new分配内存时,才能使用auto_ptr,shared_ptr

使用new[]时,只能使用unique_ptr

使用智能指针

auto_ptr

auto_ptr是C++98中引入的智能指针,用于管理动态分配的对象。虽然它已经被废弃,但仍可以了解其用法。以下是使用auto_ptr的一般步骤:

  1. 包含头文件:要使用auto_ptr,首先需要包含头文件

  2. 创建对象:使用new关键字创建一个对象,并将其分配给auto_ptr指针。

    std::auto_ptr myPtr(new int);
    

  3. 使用对象:使用auto_ptr指针操作对象。

    *myPtr = 10;
    std::cout << *myPtr << std::endl;
    

  4. 所有权转移:可以将auto_ptr指针赋值给其他auto_ptr对象,从一个对象转移到另一个对象。这将导致原来的auto_ptr对象为空指针。

    std::auto_ptr myPtr2 = myPtr;
    

  5. 销毁对象:当auto_ptr对象超出范围时,它会自动释放其所拥有的对象。这将调用被动指针的析构函数,并使用delete操作符来释放内存。

  6. 注意事项:

    • auto_ptr对象不是线程安全的,因此不建议在多线程环境下使用。
    • auto_ptr对象存在潜在的问题,如悬空指针和内存泄漏。因此,C++11引入了更安全和更灵活的智能指针unique_ptr来替代auto_ptr。

请注意,尽管auto_ptr可能看起来很简单,但由于其存在的问题和局限性,建议使用更现代的智能指针unique_ptr或shared_ptr来代替auto_ptr。

unique_ptr

unique_ptr是C++11中引入的智能指针,用于管理动态分配的对象。它提供了独占式所有权,只能有一个unique_ptr指向特定的对象。以下是使用unique_ptr的一般步骤:

  1. 包含头文件:要使用unique_ptr,首先需要包含头文件

  2. 创建对象:使用new关键字创建一个对象,并将其分配给unique_ptr指针。

    std::unique_ptr myPtr(new int);
    

  3. 使用对象:使用unique_ptr指针操作对象。

    *myPtr = 10;
    std::cout << *myPtr << std::endl;
    

  4. 所有权转移:可以将unique_ptr指针赋值给其他unique_ptr对象,从一个对象转移到另一个对象。这会导致原来的unique_ptr对象为空指针。

    std::unique_ptr myPtr2 = std::move(myPtr);
    

  5. 销毁对象:当unique_ptr对象超出范围时,它会自动释放其所拥有的对象。这将调用unique_ptr的析构函数,并使用delete操作符来释放内存。

  6. 使用自定义删除器:unique_ptr可以使用自定义的删除器来释放对象。可以使用lambda表达式、函数指针或函数对象作为删除器。

    std::unique_ptr myPtr(new int, [](int* p) { delete p; });
    

    或者使用std::default_delete作为默认的删除器。

    std::unique_ptr myPtr(new int); // 默认使用std::default_delete
    

请注意,unique_ptr提供了更严格的所有权管理,不允许多个指针指向同一对象,因此可以更安全地使用和传递指针。它还提供了移动语义,允许高效地转移所有权。这使得unique_ptr成为首选的智能指针类型。

同时,unique_ptr还提供了make_unique函数(C++14及以上版本),用于更方便地创建和初始化unique_ptr对象。

auto myPtr = std::make_unique(10);

总而言之,unique_ptr是一种推荐使用的智能指针,可以更安全地管理动态分配的对象,并提供了更好的语义和性能。

shared_ptr

shared_ptr是C++11中引入的智能指针,用于共享对象的所有权。它可以有多个shared_ptr指向同一个对象,当最后一个指针超出范围时,对象才会被销毁。以下是使用shared_ptr的一般步骤:

  1. 包含头文件:要使用shared_ptr,首先需要包含头文件

  2. 创建对象:可以使用make_shared函数(C++11及以上版本)或直接使用shared_ptr构造函数来创建对象。

    std::shared_ptr myPtr = std::make_shared(10);
    

  3. 使用对象:使用shared_ptr指针操作对象。

    *myPtr = 20;
    std::cout << *myPtr << std::endl;
    

  4. 共享所有权:可以将shared_ptr指针赋值给其他shared_ptr对象,使它们共享相同的对象。

    std::shared_ptr myPtr2 = myPtr;
    

  5. 获取引用计数:可以使用use_count函数获取当前有多少个shared_ptr指向相同的对象。

    std::cout << myPtr.use_count() << std::endl;
    

  6. 重置指针:可以使用reset函数将shared_ptr指针重置为空指针或指向其他对象。

    myPtr.reset();
    

  7. 自定义删除器:shared_ptr可以使用自定义的删除器来释放对象。可以使用lambda表达式、函数指针或函数对象作为删除器。

    std::shared_ptr myPtr(new int, [](int* p) { delete p; });
    

    或者使用std::default_delete作为默认的删除器。

    std::shared_ptr myPtr(new int); // 默认使用std::default_delete
    

总而言之,shared_ptr是一种用于共享对象所有权的智能指针,可以安全地管理对象的生命周期,避免内存泄漏和野指针的问题。它是一种非常强大和实用的智能指针类型,特别适用于多个指针需要共享同一个对象的情况。

weak_ptr

weak_ptr是C++11中引入的智能指针,用于解决shared_ptr的循环引用问题。它是一种弱引用,不会增加对象的引用计数。以下是使用weak_ptr的一般步骤:

  1. 包含头文件:要使用weak_ptr,首先需要包含头文件

  2. 创建weak_ptr:可以通过将shared_ptr转换为weak_ptr来创建weak_ptr对象。可以使用lock函数将weak_ptr转换为shared_ptr来操作对象。

    std::shared_ptr mySharedPtr = std::make_shared(10);
    std::weak_ptr myWeakPtr(mySharedPtr);
    

  3. 检查对象是否有效:可以使用expired函数来检查对象是否有效,即是否已经被销毁。

    if (myWeakPtr.expired()) {
        std::cout << "Weak pointer is expired." << std::endl;
    } else {
        std::cout << "Weak pointer is still valid." << std::endl;
    }
    

  4. 获取对象:可以使用lock函数将weak_ptr转换为shared_ptr来操作对象。该函数会返回一个有效的shared_ptr,或者一个空的shared_ptr(如果对象已经被销毁)。

    std::shared_ptr mySharedPtr2 = myWeakPtr.lock();
    if (mySharedPtr2) {
        // 对象仍然有效
        *mySharedPtr2 = 20;
        std::cout << *mySharedPtr2 << std::endl;
    } else {
        // 对象已经被销毁
        std::cout << "Weak pointer is expired." << std::endl;
    }
    

总而言之,weak_ptr是一种弱引用的智能指针,用于解决shared_ptr的循环引用问题。它可以用来检查对象是否有效,并且可以通过lock函数获取对象的shared_ptr以进行操作。使用weak_ptr可以避免循环引用导致的内存泄漏问题。

weak_ptr是C++11引入的一种智能指针,具有以下特点:

  1. 弱引用:weak_ptr是对shared_ptr的弱引用,不会增加引用计数。它允许我们观察和访问由shared_ptr管理的对象,但不会拥有对象的所有权。当最后一个shared_ptr析构时,weak_ptr将自动失效。

  2. 防止循环引用:weak_ptr可以用于解决shared_ptr的循环引用问题。如果两个对象相互持有shared_ptr指针,它们之间将形成循环引用,导致内存泄漏。通过将其中一个对象的SharedPtr转换为weak_ptr,可以打破循环引用,防止内存泄漏。

  3. 安全使用:当通过weak_ptr访问对象时,需要进行有效性检查。通过调用weak_ptr的expired()方法,可以检查weak_ptr是否过期(即shared_ptr是否已被释放)。如果weak_ptr未过期,可以通过调用lock()方法获取一个可用的shared_ptr来访问对象。

  4. 不拥有对象所有权:weak_ptr不拥有对象的所有权,因此不能直接操作对象或调用对象的方法。它只是提供一种观察和访问由shared_ptr管理的对象的方式。

总的来说,weak_ptr提供了一种安全地观察由shared_ptr管理的对象的方式,并且可以避免循环引用导致的内存泄漏问题。它在某些场景下非常有用,但需要注意有效性检查和使用时的限制。

你可能感兴趣的:(c++,c++,开发语言)