C++ 智能指针详解

tech2022-10-26  124

目录

unique_ptr 的使用

shared_ptr 的使用

weak_ptr 的使用


简介

智能指针仅用于存储在堆中分配的内存的对象的地址,智能指针做的工作比原始指针多得多。智能指针最重要的是特性是不必使用 delete 和 delete[] 运算符释放内存,只要不再需要智能指针,它们就会自动释放。这样就可以避免多次释放、分配/释放不匹配、指针悬挂和内存泄露的可能性。

智能指针特别适合用于管理动态创建的类对象,但智能指针不能完成原始指针的所有操作,智能指针不能递增或递减,也不能执行其他算术操作。智能指针由标准库 memory 头文件中的模版定义,所以必须在源文件中包含这个头文件,才能使用智能指针。 std 名称空间中定义了 3 种智能指针( auto_ptr 在 C++11 中弃用,在 C++17 中移除):

unique_ptr<T>

unique_ptr<T> 对象是指向T类型的指针,是唯一的,这表示不能有多个 unique_ptr<T> 对象保存相同的地址。unique_ptr<T> 对象独占地拥有指向的内容。编译器通过不允许复制 unique_ptr<T> 来强制这种唯一性。

 

shared_ptr<T>

shared_ptr<T> 对象指向T类型的指针,但与 unique_ptr<T> 不同,可以有任意多个 shared_ptr<T> 对象包含或共享相同的地址。因此,shared_ptr<T> 对象允许共享自由存储区(堆)中的对象的所有权。在任何运行时刻都知道包含给定地址的 shared_ptr<T> 对象的个数,称为引用计数。每次创建包含给定堆中存储的新 shared_ptr<T> 对象时,包含该地址的 shared_ptr<T> 的引用计数会递增;包含该地址的 shared_ptr<T> 对象被释放或指向另一个地址时,该引用计数会递减。没有包含给定地址的 shared_ptr<T> 对象时,引用计数为 0,位于该地址的对象的内存会自动释放。指向相同地址的所有 shared_ptr<T> 对象都可以访问引用计数。

 

weak_ptr<T>

weak_ptr<T> 被链接到 shared_ptr<T> 上,包含相同的地址。创建 weak_ptr<T> 不会递增所链接的 shared_ptr<T> 对象的引用计数,所以不能防止它指向的对象被释放。引用它的最后一个 shared_ptr<T> 对象释放或重置为指向另一个地址时,即使链接的 weak_ptr<T> 对象仍然存在,其内存也会释放。即使这种情况发生了,weak_ptr<T> 也不会包含悬挂指针。编译器首先会强制从 weak_ptr<T> 创建一个 shared_ptr<T> 对象来引用相同的地址。如果 weak_ptr<T> 引用的内存地址依然有效,强制创建的 shared_ptr<T> 首先会确保引用计数的值后再递增,并且指针可被安全使用。但是,如果内存已经释放,创建的 shared_ptr<T> 将包含 nullptr。

 

 

unique_ptr 的使用

template<     class T,     class Deleter = std::default_delete<T> > class unique_ptr; template <     class T,     class Deleter > class unique_ptr<T[], Deleter>;

std::unique_ptr 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针。发生下列情况时释放对象:

(1) 销毁了管理的 unique_ptr 对象。

(2) 通过 operator= 或 reset() 赋值另一指针给管理的 unique_ptr 对象。

 

智能指针对象的创建和初始化可以使用以下方法:

(1) std::unique_ptr<double> pdata( new double(99.9) ); (2) std::unique_ptr<double> pdata( std::make_unique<double>(99.9) ); (3) auto pdata( std::make_unique<double>(99.9) ); #include <iostream> #include <memory> struct Foo { Foo() { std::cout << "Foo\n"; } ~Foo() { std::cout << "~Foo\n"; } }; int main() { std::unique_ptr<Foo> p1; { std::cout << "Creating new Foo...\n"; std::unique_ptr<Foo> p2( std::make_unique<Foo>() ); // p1 = p2; // 错误!不能复制 unique_ptr p1 = std::move(p2); std::cout << "About to leave inner block...\n"; // Foo 实例将继续生存,尽管 p2 离开作用域 } std::cout << "About to leave program...\n"; } 输出: Creating new Foo... Foo About to leave inner block... About to leave program... ~Foo

 

get()

pointer get() const noexcept;

返回指向被管理对象的指针,如果无被管理对象,则为 nullptr 。只应该在一种情况下访问智能指针中保存的原始指针:要将其传递给仅仅短暂使用该指针的函数。

#include <iostream> #include <string> #include <memory> int main() { std::unique_ptr<std::string> s_p(new std::string("Hello, world!")); std::string *s = s_p.get(); std::cout << *s << '\n'; }

 

reset()

初等模板 unique_ptr<T> 的成员 (1)void reset( pointer ptr = pointer() ) noexcept; 特化 unique_ptr<T[]> 的成员 (2)void reset( pointer ptr = pointer() ) noexcept; (3)template< class U > void reset( U ) noexcept; (4)void reset( std::nullptr_t p = nullptr ) noexcept;

reset() 替换被管理对象,调用 reset() 函数可以把任意类型的智能指针包含的指针重置为指向 nullptr 或修改其指向的值。

#include <iostream> #include <memory> struct Foo { Foo() { std::cout << "Foo...\n"; } ~Foo() { std::cout << "~Foo...\n"; } }; struct D { void operator() (Foo* p) { std::cout << "Calling delete for Foo object... \n"; delete p; } }; int main() { std::cout << "Creating new Foo...\n"; std::unique_ptr<Foo, D> up(new Foo(), D()); // up 占有 Foo 指针(删除器 D ) std::cout << "Replace owned Foo with a new Foo...\n"; up.reset(new Foo()); // 调用旧者的删除器 std::cout << "Release and delete the owned Foo...\n"; up.reset(nullptr); }

 

release()

pointer release() noexcept;

指针所指的对象存在,则释放被管理对象的所有权。调用release()后 get() 返回 nullptr 。此时,需要由程序员自己负责删除该对象。

#include <memory> #include <iostream> #include <cassert> struct Foo { Foo() { std::cout << "Foo\n"; } ~Foo() { std::cout << "~Foo\n"; } }; int main() { std::cout << "Creating new Foo...\n"; std::unique_ptr<Foo> up(new Foo()); std::cout << "About to release Foo...\n"; Foo* fp = up.release(); assert (up.get() == nullptr); std::cout << "Foo is no longer owned by unique_ptr...\n"; delete fp; }

 

swap()

void swap(unique_ptr& other) noexcept;

交换 *this 和另一 unique_ptr 对象的管理对象。

#include <iostream> #include <memory> struct Foo { Foo(int _val) : val(_val) { std::cout << "Foo...\n"; } ~Foo() { std::cout << "~Foo...\n"; } int val; }; int main() { std::unique_ptr<Foo> up1(new Foo(1)); std::unique_ptr<Foo> up2(new Foo(2)); up1.swap(up2); std::cout << "up1->val:" << up1->val << std::endl; std::cout << "up2->val:" << up2->val << std::endl; }

 

get_deleter()

Deleter& get_deleter() noexcept; const Deleter& get_deleter() const noexcept;

返回会用于析构被管理对象的删除器。

#include <iostream> #include <memory> struct Foo { Foo() { std::cout << "Foo...\n"; } ~Foo() { std::cout << "~Foo...\n"; } }; struct D { void bar() { std::cout << "Call deleter D::bar()...\n"; } void operator()(Foo* p) const { std::cout << "Call delete for Foo object...\n"; delete p; } }; int main() { std::unique_ptr<Foo, D> up(new Foo(), D()); D& del = up.get_deleter(); del.bar(); }

 

operator bool

explicit operator bool() const noexcept;

检查 *this 是否占有对象,即是否有 get() != nullptr 。

#include <iostream> #include <memory> int main() { std::unique_ptr<int> ptr(new int(42)); if (ptr) std::cout << "before reset, ptr is: " << *ptr << '\n'; ptr.reset(); if (ptr) std::cout << "after reset, ptr is: " << *ptr << '\n'; }

 

operator *

typename std::add_lvalue_reference<T>::type operator*() const; pointer operator->() const noexcept;

operator* 与 operator-> 提供到 *this 所占有的对象的访问。

#include <iostream> #include <memory> struct Foo { void bar() { std::cout << "Foo::bar\n"; } }; void f(const Foo& foo) { std::cout << "f(const Foo&)\n"; } int main() { std::unique_ptr<Foo> ptr(new Foo); ptr->bar(); f(*ptr); }

 

operator[ ]

T& operator[](size_t i) const;

operator[] 提供对 unique_ptr 所管理的数组元素的访问。

#include <iostream> #include <memory> int main() { const int size = 10; std::unique_ptr<int[]> fact(new int[size]); for (int i = 0; i < size; ++i) { fact[i] = (i == 0) ? 1 : i * fact[i-1]; } for (int i = 0; i < size; ++i) { std::cout << i << ": " << fact[i] << '\n'; } }

 

 

shared_ptr 的使用

template< class T > class shared_ptr;

std::shared_ptr 是通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可占有同一对象。下列情况之一出现时,会销毁对象并释放其内存:

(1) 最后一个占有对象的 shared_ptr 被销毁;

(2) 最后一个占有对象的 shared_ptr 被通过 operator= 或 reset() 赋值为另一指针。

 

reset()

(1)void reset() noexcept; (2)template< class Y > void reset( Y* ptr ); (3)template< class Y, class Deleter > void reset( Y* ptr, Deleter d ); (4)template< class Y, class Deleter, class Alloc > void reset( Y* ptr, Deleter d, Alloc alloc );

reset() 替换被管理对象,调用 reset() 函数可以把任意类型的智能指针包含的指针重置为指向 nullptr 或修改其指向的值。

 

swap()

void swap( shared_ptr& r ) noexcept;

swap 交换 *this 与 r 的内容。

 

operator* 、 operator-> 和 operator[]

T& operator*() const noexcept; T* operator->() const noexcept; element_type& operator[]( std::ptrdiff_t idx ) const; #include <iostream> #include <memory> struct Foo { Foo(int in) : a(in) {} void print() const { std::cout << "a = " << a << '\n'; } int a; }; int main() { auto ptr = std::make_shared<Foo>(10); ptr->print(); (*ptr).print(); }

 

use_count()

long use_count() const noexcept;

返回管理当前对象的不同 shared_ptr 实例(包含 this )数量。若无管理对象,则返回 0 。

#include <memory> #include <iostream> void fun(std::shared_ptr<int> sp) { std::cout << "fun: sp.use_count() == " << sp.use_count() << '\n'; } int main() { auto sp1 = std::make_shared<int>(5); std::cout << "sp1.use_count() == " << sp1.use_count() << '\n'; fun(sp1); }

 

operator bool

explicit operator bool() const noexcept;

检查 *this 是否存储非空指针,即是否有 get() != nullptr 。

#include <iostream> #include <memory> void report(std::shared_ptr<int> ptr) { if (ptr) { std::cout << "*ptr=" << *ptr << "\n"; } else { std::cout << "ptr is not a valid pointer.\n"; } } int main() { std::shared_ptr<int> ptr; report(ptr); ptr = std::make_shared<int>(7); report(ptr); }

 

 

weak_ptr 的使用

template< class T > class weak_ptr;

std::weak_ptr 是一种智能指针,它对被 std::shared_ptr 管理的对象存在非拥有性("弱")引用。在访问所引用的对象前必须先转换为 std::shared_ptr

std::weak_ptr 用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用 std::weak_ptr 来跟踪该对象。需要获得临时所有权时,则将其转换为 std::shared_ptr,此时如果原来的 std::shared_ptr 被销毁,则该对象的生命期将被延长至这个临时的 std::shared_ptr 同样被销毁为止。

std::weak_ptr 的另一用法是打断 std::shared_ptr 所管理的对象组成的环状引用。若这种环被孤立(例如无指向环中的外部共享指针),则 shared_ptr 引用计数无法抵达零,而内存被泄露。能令环中的指针之一为弱指针以避免此情况。

 

reset()

void reset() noexcept;

释放被管理对象的所有权。调用 reset() 后,*this 不管理对象。

 

swap()

void swap( weak_ptr& r ) noexcept;

交换 *this 与 r 的内容。

 

use_count()

long use_count() const noexcept;

返回共享被管理对象所有权的 shared_ptr 实例数量,或 0 ,若被管理对象已被删除,即 *this 为空。

#include <iostream> #include <memory> std::weak_ptr<int> gwp; void observe_gwp() { std::cout << "use_count(): " << gwp.use_count() << "\t id: "; if (auto sp = gwp.lock()) std::cout << *sp << '\n'; else std::cout << "?\n"; } void share_recursively(std::shared_ptr<int> sp, int depth) { observe_gwp(); // : 1 2 3 4 if (1 < depth) share_recursively(sp, depth - 1); observe_gwp(); // : 4 3 2 } int main() { observe_gwp(); { auto sp = std::make_shared<int>(42); gwp = sp; observe_gwp(); // : 0 share_recursively(sp, 3); // : 1 2 3 4 4 3 2 observe_gwp(); // : 1 } observe_gwp(); // : 0 }

 

expired()

bool expired() const noexcept;

等价于 use_count() == 0 。可能仍未对被管理对象调用析构函数,但此对象的析构已经临近(或可能已发生)。

#include <iostream> #include <memory> std::weak_ptr<int> gw; void f() { if (!gw.expired()) { std::cout << "gw is valid\n"; } else { std::cout << "gw is expired\n"; } } int main() { { auto sp = std::make_shared<int>(42); gw = sp; f(); } f(); }

 

lock()

std::shared_ptr<T> lock() const noexcept;

weak_ptr 是一种不控制所指向对象生存期的智能指针,它指向一个由 shared_ptr 管理的对象,将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数。一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放,即使有 weak_ptr 指向对象,对象还是会被释放。

由于对象可能不存在,我们不能使用 weak_ptr 直接访问对象,而必须调用 lock,此函数检查 weak_ptr 指向的对象是否存在。如果存在,lock 返回一个指向共享对象的 shared_ptr如果不存在,lock 将返回一个空指针

#include <iostream> #include <memory> void observe(std::weak_ptr<int> weak) { if (auto observe = weak.lock()) { std::cout << "\tobserve() able to lock weak_ptr<>, value=" << *observe << "\n"; } else { std::cout << "\tobserve() unable to lock weak_ptr<>\n"; } } int main() { std::weak_ptr<int> weak; std::cout << "weak_ptr<> not yet initialized\n"; observe(weak); { auto shared = std::make_shared<int>(42); weak = shared; std::cout << "weak_ptr<> initialized with shared_ptr.\n"; observe(weak); } std::cout << "shared_ptr<> has been destructed due to scope exit.\n"; observe(weak); }

 

参考:cppreference.com

最新回复(0)