C++ 스마트 포인터에 대한 자세한 설명
1. 소개
C++ 언어에는 자동 메모리 재활용 메커니즘이 없으므로 프로그래머는 new를 사용해야 합니다. 매번 수동으로 삭제하세요. 프로그래머가 삭제하는 것을 잊어버리는 것은 드문 일이 아니며 프로세스가 너무 복잡하고 궁극적으로 삭제가 이루어지지 않으며 예외로 인해 삭제를 실행하지 않고 프로그램이 조기 종료되는 경우가 있습니다.
스마트 포인터를 사용하면 이러한 문제를 효과적으로 해결할 수 있습니다. 이 글에서는 주로 스마트 포인터의 사용법을 설명합니다. 포함: std::auto_ptr, Boost::scoped_ptr, Boost::shared_ptr, Boost::scoped_array, Boost::shared_array, Boost::weak_ptr, Boost::intrusive_ptr. 새로운 문제를 해결하고 일치하는 문제를 삭제하는 데 정말 많은 스마트 포인터가 필요한가?라고 생각할 수도 있습니다. 이 글을 읽고 나면 자연스럽게 답이 떠오르실 거라 생각합니다.
다음 7가지 스마트 포인터(smart_ptr)에 대해 순서대로 설명하겠습니다.
2. 구체적인 용도
1. 요약
컴파일러의 경우 스마트 포인터는 실제로 포인터 유형이 아닌 스택 객체입니다. , 스택 개체의 수명이 거의 끝나갈 때 스마트 포인터는 소멸자를 통해 자신이 관리하는 힙 메모리를 해제합니다. 모든 스마트 포인터는 개체에 대한 참조를 직접 반환하고 개체를 작동하는 데 사용되는 "연산자->" 연산자로 오버로드됩니다. 스마트 포인터에 액세스하는 원래 방법은 "." 연산자를 사용합니다.
스마트 포인터에 포함된 원시 포인터에 액세스하려면 get() 함수를 사용할 수 있습니다. 스마트 포인터는 객체이므로 if(my_smart_object)는 항상 true입니다. 스마트 포인터의 원시 포인터가 null인지 확인하려면 if(my_smart_object.get())와 같이 확인해야 합니다.
스마트 포인터에는 재설정() 메서드가 포함되어 있습니다. 매개 변수가 전달되지 않거나 NULL이 전달되면 스마트 포인터는 현재 관리되는 메모리를 해제합니다. 객체가 전달되면 스마트 포인터는 현재 객체를 해제하여 새로 전달된 객체를 관리합니다.
분석을 돕기 위해 테스트 클래스를 작성합니다:
class Simple { public: Simple(int param = 0) { number = param; std::cout << "Simple: " << number << std::endl; } ~Simple() { std::cout << "~Simple: " << number << std::endl; } void PrintSomething() { std::cout << "PrintSomething: " << info_extend.c_str() << std::endl; } std::string info_extend; int number; };
2. 🎜>
std::auto_ptr은 STL에 속하며 물론 헤더파일 #include를 포함시켜 std 네임스페이스에서도 사용할 수 있다. std::auto_ptr은 단일 힙 메모리 개체를 편리하게 관리할 수 있습니다.
코드 분석으로 시작합니다:
void TestAutoPtr() { std::auto_ptr<Simple> my_memory(new Simple(1)); // 创建对象,输出:Simple:1 if (my_memory.get()) { // 判断智能指针是否为空 my_memory->PrintSomething(); // 使用 operator-> 调用智能指针对象中的函数 my_memory.get()->info_extend = "Addition"; // 使用 get() 返回裸指针,然后给内部对象赋值 my_memory->PrintSomething(); // 再次打印,表明上述赋值成功 (*my_memory).info_extend += " other"; // 使用 operator* 返回智能指针内部对象,然后用“.”调用智能指针对象中的函数 my_memory->PrintSomething(); // 再次打印,表明上述赋值成功 } } // my_memory 栈对象即将结束生命期,析构堆对象 Simple(1)
실행 결과:
Simple: 1 PrintSomething: PrintSomething: Addition PrintSomething: Addition other ~Simple: 1
위는 std::auto_ptr의 일반적인 사용을 위한 코드입니다. 어쨌든 명시적으로 삭제를 사용할 필요는 없습니다.
사실 좋은 시절은 오래가지 못했습니다. 다음과 같은 또 다른 예를 살펴보겠습니다.
void TestAutoPtr2() { std::auto_ptr<Simple> my_memory(new Simple(1)); if (my_memory.get()) { std::auto_ptr<Simple> my_memory2; // 创建一个新的 my_memory2 对象 my_memory2 = my_memory; // 复制旧的 my_memory 给 my_memory2 my_memory2->PrintSomething(); // 输出信息,复制成功 my_memory->PrintSomething(); // 崩溃 } }
마지막으로 위의 코드는 충돌을 일으켰습니다. 위의 코드는 C++ 프로그래밍 아이디어와 완전히 일치하지만 실제로는 std::auto_ptr의 소스 코드를 따라가면 "my_memory2 = my_memory"라는 것을 알 수 있습니다. 이 코드 줄, my_memory2는 my_memory 소유권의 메모리 관리를 완전히 인수하여 my_memory가 매달려 있게 하여 마지막 사용 시 충돌을 일으킵니다.
따라서 std::auto_ptr을 사용할 때는 "operator=" 연산자를 사용하면 안 됩니다. 라이브러리로서 사용자는 이를 사용할 수 없으며 명시적으로 거부되지도 않습니다[1]. 이는 다소 예상치 못한 일입니다.
std::auto_ptr의 첫 번째 예를 읽은 후 또 다른 예를 살펴보겠습니다.
void TestAutoPtr3() { std::auto_ptr<Simple> my_memory(new Simple(1)); if (my_memory.get()) { my_memory.release(); } }
실행 결과는 다음과 같습니다.
Simple: 1
뭔가 특이한 점을 보셨나요? 우리가 만든 객체는 소멸되지 않았고 "~Simple: 1"이 출력되지 않아 메모리 누수가 발생했습니다. my_memory가 계속 유지되는 것을 원하지 않을 때 release() 함수를 호출하여 메모리를 해제합니다. 이로 인해 메모리 누수가 발생합니다(메모리가 제한된 시스템에서 my_memory가 너무 많은 메모리를 차지하면 반환을 고려할 것입니다). my_memory의 수명이 다할 때까지 기다리지 않고 반환됩니다.)
올바른 코드는
void TestAutoPtr3() { std::auto_ptr<Simple> my_memory(new Simple(1)); if (my_memory.get()) { Simple* temp_memory = my_memory.release(); delete temp_memory; } }
또는
void TestAutoPtr3() { std::auto_ptr<Simple> my_memory(new Simple(1)); if (my_memory.get()) { my_memory.reset(); // 释放 my_memory 内部管理的内存 } }
std::auto_ptr의 release() 함수는 단지 메모리 소유권을 포기하는 것으로 나타났습니다. 이는 분명히 C++ 프로그래밍 아이디어와 일치하지 않습니다.
std::auto_ptr을 사용하는 데에는 많은 제한이 있습니다. 힙 메모리 배열을 관리하는 데 사용할 수 없습니다. 지금 생각해보면 제약이 꽤 많은 것 같아요. 조심하지 않으면 언젠가는 문제가 생길 거에요.
std::auto_ptr이 많은 문제를 일으켰기 때문에 일부 디자인은 C++ 프로그래밍 아이디어와 그다지 일치하지 않으므로 다음 부스트 스마트 포인터가 트리거될 수 있습니다. 위의 문제 문제를 해결하세요.
계속 아래를 내려다보자.
3. Boost::scoped_ptr
boost::scoped_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件 #includee23b372897c2668fffd6001e3ccdaabb 便可以使用。boost::scoped_ptr 跟 std::auto_ptr 一样,可以方便的管理单个堆内存对象,特别的是,boost::scoped_ptr 独享所有权,避免了 std::auto_ptr 恼人的几个问题。
我们还是从代码开始分析:
void TestScopedPtr() { boost::scoped_ptr<Simple> my_memory(new Simple(1)); if (my_memory.get()) { my_memory->PrintSomething(); my_memory.get()->info_extend = "Addition"; my_memory->PrintSomething(); (*my_memory).info_extend += " other"; my_memory->PrintSomething(); my_memory.release(); // 编译 error: scoped_ptr 没有 release 函数 std::auto_ptr<Simple> my_memory2; my_memory2 = my_memory; // 编译 error: scoped_ptr 没有重载 operator=,不会导致所有权转移 } }
首先,我们可以看到,boost::scoped_ptr 也可以像 auto_ptr 一样正常使用。但其没有 release() 函数,不会导致先前的内存泄露问题。其次,由于 boost::scoped_ptr 是独享所有权的,所以明确拒绝用户写“my_memory2 = my_memory”之类的语句,可以缓解 std::auto_ptr 几个恼人的问题。
由于 boost::scoped_ptr 独享所有权,当我们真真需要复制智能指针时,需求便满足不了了,如此我们再引入一个智能指针,专门用于处理复制,参数传递的情况,这便是如下的 boost::shared_ptr。
4、boost::shared_ptr
boost::shared_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件 #includee23b372897c2668fffd6001e3ccdaabb 便可以使用。在上面我们看到 boost::scoped_ptr 独享所有权,不允许赋值、拷贝,boost::shared_ptr 是专门用于共享所有权的,由于要共享所有权,其在内部使用了引用计数。boost::shared_ptr 也是用于管理单个堆内存对象的。
我们还是从代码开始分析:
void TestSharedPtr(boost::shared_ptr<Simple> memory) { // 注意:无需使用 reference (或 const reference) memory->PrintSomething(); std::cout << "TestSharedPtr UseCount: " << memory.use_count() << std::endl; } void TestSharedPtr2() { boost::shared_ptr<Simple> my_memory(new Simple(1)); if (my_memory.get()) { my_memory->PrintSomething(); my_memory.get()->info_extend = "Addition"; my_memory->PrintSomething(); (*my_memory).info_extend += " other"; my_memory->PrintSomething(); } std::cout << "TestSharedPtr2 UseCount: " << my_memory.use_count() << std::endl; TestSharedPtr(my_memory); std::cout << "TestSharedPtr2 UseCount: " << my_memory.use_count() << std::endl; //my_memory.release();// 编译 error: 同样,shared_ptr 也没有 release 函数 }
执行结果为:
Simple: 1 PrintSomething: PrintSomething: Addition PrintSomething: Addition other TestSharedPtr2 UseCount: 1 PrintSomething: Addition other TestSharedPtr UseCount: 2 TestSharedPtr2 UseCount: 1 ~Simple: 1
boost::shared_ptr 也可以很方便的使用。并且没有 release() 函数。关键的一点,boost::shared_ptr 内部维护了一个引用计数,由此可以支持复制、参数传递等。boost::shared_ptr 提供了一个函数 use_count() ,此函数返回 boost::shared_ptr 内部的引用计数。查看执行结果,我们可以看到在 TestSharedPtr2 函数中,引用计数为 1,传递参数后(此处进行了一次复制),在函数TestSharedPtr 内部,引用计数为2,在 TestSharedPtr 返回后,引用计数又降低为 1。当我们需要使用一个共享对象的时候,boost::shared_ptr 是再好不过的了。
在此,我们已经看完单个对象的智能指针管理,关于智能指针管理数组,我们接下来讲到。
5、boost::scoped_array
boost::scoped_array 属于 boost 库,定义在 namespace boost 中,包含头文件 #includee23b372897c2668fffd6001e3ccdaabb 便可以使用。
boost::scoped_array 便是用于管理动态数组的。跟 boost::scoped_ptr 一样,也是独享所有权的。
我们还是从代码开始分析:
void TestScopedArray() { boost::scoped_array<Simple> my_memory(new Simple[2]); // 使用内存数组来初始化 if (my_memory.get()) { my_memory[0].PrintSomething(); my_memory.get()[0].info_extend = "Addition"; my_memory[0].PrintSomething(); (*my_memory)[0].info_extend += " other"; // 编译 error,scoped_ptr 没有重载 operator* my_memory[0].release(); // 同上,没有 release 函数 boost::scoped_array<Simple> my_memory2; my_memory2 = my_memory; // 编译 error,同上,没有重载 operator= } }
boost::scoped_array 的使用跟 boost::scoped_ptr 差不多,不支持复制,并且初始化的时候需要使用动态数组。另外,boost::scoped_array 没有重载“operator*”,其实这并无大碍,一般情况下,我们使用 get() 函数更明确些。
下面肯定应该讲 boost::shared_array 了,一个用引用计数解决复制、参数传递的智能指针类。
6、boost::shared_array
boost::shared_array 属于 boost 库,定义在 namespace boost 中,包含头文件 #includee23b372897c2668fffd6001e3ccdaabb 便可以使用。
由于 boost::scoped_array 独享所有权,显然在很多情况下(参数传递、对象赋值等)不满足需求,由此我们引入 boost::shared_array。跟 boost::shared_ptr 一样,内部使用了引用计数。
我们还是从代码开始分析:
void TestSharedArray(boost::shared_array<Simple> memory) { // 注意:无需使用 reference (或 const reference) std::cout << "TestSharedArray UseCount: " << memory.use_count() << std::endl; } void TestSharedArray2() { boost::shared_array<Simple> my_memory(new Simple[2]); if (my_memory.get()) { my_memory[0].PrintSomething(); my_memory.get()[0].info_extend = "Addition 00"; my_memory[0].PrintSomething(); my_memory[1].PrintSomething(); my_memory.get()[1].info_extend = "Addition 11"; my_memory[1].PrintSomething(); //(*my_memory)[0].info_extend += " other"; // 编译 error,scoped_ptr 没有重载 operator* } std::cout << "TestSharedArray2 UseCount: " << my_memory.use_count() << std::endl; TestSharedArray(my_memory); std::cout << "TestSharedArray2 UseCount: " << my_memory.use_count() << std::endl; }
执行结果为:
Simple: 0 Simple: 0 PrintSomething: PrintSomething: Addition 00 PrintSomething: PrintSomething: Addition 11 TestSharedArray2 UseCount: 1 TestSharedArray UseCount: 2 TestSharedArray2 UseCount: 1 ~Simple: 0 ~Simple: 0
跟 boost::shared_ptr 一样,使用了引用计数,可以复制,通过参数来传递。
至此,我们讲过的智能指针有 std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array。这几个智能指针已经基本够我们使用了,90% 的使用过标准智能指针的代码就这 5 种。可如下还有两种智能指针,它们肯定有用,但有什么用处呢,一起看看吧。
7、boost::weak_ptr
boost::weak_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件 #includee23b372897c2668fffd6001e3ccdaabb 便可以使用。
在讲 boost::weak_ptr 之前,让我们先回顾一下前面讲解的内容。似乎 boost::scoped_ptr、boost::shared_ptr 这两个智能指针就可以解决所有单个对象内存的管理了,这儿还多出一个 boost::weak_ptr,是否还有某些情况我们没纳入考虑呢?
回答:有。首先 boost::weak_ptr 是专门为 boost::shared_ptr 而准备的。有时候,我们只关心能否使用对象,并不关心内部的引用计数。boost::weak_ptr 是 boost::shared_ptr 的观察者(Observer)对象,观察者意味着 boost::weak_ptr 只对 boost::shared_ptr 进行引用,而不改变其引用计数,当被观察的 boost::shared_ptr 失效后,相应的 boost::weak_ptr 也相应失效。
我们还是从代码开始分析:
void TestWeakPtr() { boost::weak_ptr<Simple> my_memory_weak; boost::shared_ptr<Simple> my_memory(new Simple(1)); std::cout << "TestWeakPtr boost::shared_ptr UseCount: " << my_memory.use_count() << std::endl; my_memory_weak = my_memory; std::cout << "TestWeakPtr boost::shared_ptr UseCount: " << my_memory.use_count() << std::endl; }
执行结果为:
Simple: 1 TestWeakPtr boost::shared_ptr UseCount: 1 TestWeakPtr boost::shared_ptr UseCount: 1 ~Simple: 1
我们看到,尽管被赋值了,内部的引用计数并没有什么变化,当然,读者也可以试试传递参数等其他情况。
现在要说的问题是,boost::weak_ptr 到底有什么作用呢?从上面那个例子看来,似乎没有任何作用,其实 boost::weak_ptr 主要用在软件架构设计中,可以在基类(此处的基类并非抽象基类,而是指继承于抽象基类的虚基类)中定义一个 boost::weak_ptr,用于指向子类的 boost::shared_ptr,这样基类仅仅观察自己的 boost::weak_ptr 是否为空就知道子类有没对自己赋值了,而不用影响子类 boost::shared_ptr 的引用计数,用以降低复杂度,更好的管理对象。
8、boost::intrusive_ptr
boost::intrusive_ptr属于 boost 库,定义在 namespace boost 中,包含头文件 #includee23b372897c2668fffd6001e3ccdaabb 便可以使用。
讲完如上 6 种智能指针后,对于一般程序来说 C++ 堆内存管理就够用了,现在有多了一种 boost::intrusive_ptr,这是一种插入式的智能指针,内部不含有引用计数,需要程序员自己加入引用计数,不然编译不过(⊙﹏⊙b汗)。个人感觉这个智能指针没太大用处,至少我没用过。有兴趣的朋友自己研究一下源代码哦J。
三、总结
如上讲了这么多智能指针,有必要对这些智能指针做个总结:
1、在可以使用 boost 库的场合下,拒绝使用 std::auto_ptr,因为其不仅不符合 C++ 编程思想,而且极容易出错[2]。
2、在确定对象无需共享的情况下,使用 boost::scoped_ptr(当然动态数组使用 boost::scoped_array)。
3、在对象需要共享的情况下,使用 boost::shared_ptr(当然动态数组使用 boost::shared_array)。
4、在需要访问 boost::shared_ptr 对象,而又不想改变其引用计数的情况下,使用 boost::weak_ptr,一般常用于软件框架设计中。
5、最后一点,也是要求最苛刻一点:在你的代码中,不要出现 delete 关键字(或 C 语言的 free 函数),因为可以用智能指针去管理。
以上就是C++ 智能指针详解的内容,更多相关内容请关注PHP中文网(www.php.cn)!