官方参考
普通指针的烦恼:内存泄漏,多次释放,提前释放
智能指针 负责自动释放所指向的对象。
三种智能指针 shared_ptr,unique_ptr,weak_ptr;
将shared_ptr存放在一个容器中,不再需要它的时候,要erase掉。
allocator负责封装堆内存管理的对象,它们在整个标准库中使用,特别是STL容器使用它们来管理容器内部的所有内存分配,大部份情况下,程序员不用理会,标准容器使用默认的分配器称为std :: allocator。
shared_ptr
多个指针指向相同的对象;
使用引用计数,引用计数是线程安全的,但是对象的读写需要加锁。
不可以直接将指针直接赋值给一个智能指针,因为指针指针是一个类。
get获取原始指针
最大的陷阱就是循环引用,这会导致内存无法正确释放,导致内存泄漏
#include <iostream> #include <memory> #include <thread> #include <chrono> #include <mutex> struct Base { Base() { std::cout << " Base::Base()\n"; } // 注意:此处非虚析构函数 OK ~Base() { std::cout << " Base::~Base()\n"; } }; struct Derived: public Base { Derived() { std::cout << " Derived::Derived()\n"; } ~Derived() { std::cout << " Derived::~Derived()\n"; } }; void thr(std::shared_ptr<Base> p) { std::this_thread::sleep_for(std::chrono::seconds(1)); std::shared_ptr<Base> lp = p; // 线程安全,虽然自增共享的 use_count { static std::mutex io_mutex; std::lock_guard<std::mutex> lk(io_mutex); std::cout << "local pointer in a thread:\n" << " lp.get() = " << lp.get() << ", lp.use_count() = " << lp.use_count() << '\n'; } } int main() { std::shared_ptr<Base> p = std::make_shared<Derived>(); std::cout << "Created a shared Derived (as a pointer to Base)\n" << " p.get() = " << p.get() << ", p.use_count() = " << p.use_count() << '\n'; std::thread t1(thr, p), t2(thr, p), t3(thr, p); p.reset(); // 从 main 释放所有权 std::cout << "Shared ownership between 3 threads and released\n" << "ownership from main:\n" << " p.get() = " << p.get() << ", p.use_count() = " << p.use_count() << '\n'; t1.join(); t2.join(); t3.join(); std::cout << "All threads completed, the last one deleted Derived\n"; }
可能的输出:
Base::Base() Derived::Derived() Created a shared Derived (as a pointer to Base) p.get() = 0x2299b30, p.use_count() = 1 Shared ownership between 3 threads and released ownership from main: p.get() = 0, p.use_count() = 0 local pointer in a thread: lp.get() = 0x2299b30, lp.use_count() = 5 local pointer in a thread: lp.get() = 0x2299b30, lp.use_count() = 3 local pointer in a thread: lp.get() = 0x2299b30, lp.use_count() = 2 Derived::~Derived() Base::~Base() All threads completed, the last one deleted Derived
是为了配合shared_ptr而引入的一种智能指针,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。
weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。
成员函数expired()的功能等价于use_count()==0,
weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象
#include <iostream> #include <memory> std::weak_ptr<int> gw; void observe() { std::cout << "use_count == " << gw.use_count() << ": "; if (auto spt = gw.lock()) { // 使用之前必须复制到 shared_ptr std::cout << *spt << "\n"; } else { std::cout << "gw is expired\n"; } } int main() { { auto sp = std::make_shared<int>(42); gw = sp; observe(); } observe(); }
输出:
use_count == 1: 42 use_count == 0: gw is expired
unique_ptr
唯一拥有对象
通过reset方法重新指定
通过release方法释放所有权
#include <iostream> #include <vector> #include <memory> #include <cstdio> #include <fstream> #include <cassert> #include <functional> struct B { virtual void bar() { std::cout << "B::bar\n"; } virtual ~B() = default;//父类的析构函数需要定义为虚函数,防止内存泄漏 }; struct D : B { D() { std::cout << "D::D\n"; } ~D() { std::cout << "D::~D\n"; } void bar() override { std::cout << "D::bar\n"; } }; // 消费 unique_ptr 的函数能以值或以右值引用接收它 std::unique_ptr<D> pass_through(std::unique_ptr<D> p) { p->bar(); return p; } void close_file(std::FILE* fp) { std::fclose(fp); } int main() { std::cout << "unique ownership semantics demo\n"; { auto p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr auto q = pass_through(std::move(p)); assert(!p); // 现在 p 不占有任何内容并保有空指针 q->bar(); // 而 q 占有 D 对象 } // ~D 调用于此 std::cout << "Runtime polymorphism demo\n"; { std::unique_ptr<B> p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr // 作为指向基类的指针 p->bar(); // 虚派发 std::vector<std::unique_ptr<B>> v; // unique_ptr 能存储于容器 v.push_back(std::make_unique<D>()); v.push_back(std::move(p)); v.emplace_back(new D); for(auto& p: v) p->bar(); // 虚派发 } // ~D called 3 times std::cout << "Custom deleter demo\n"; std::ofstream("demo.txt") << 'x'; // 准备要读的文件 { std::unique_ptr<std::FILE, void (*)(std::FILE*) > fp(std::fopen("demo.txt", "r"), close_file); if(fp) // fopen 可以打开失败;该情况下 fp 保有空指针 std::cout << (char)std::fgetc(fp.get()) << '\n'; } // fclose() 调用于此,但仅若 FILE* 不是空指针 // (即 fopen 成功) std::cout << "Custom lambda-expression deleter demo\n"; { std::unique_ptr<D, std::function<void(D*)>> p(new D, [](D* ptr) { std::cout << "destroying from a custom deleter...\n"; delete ptr; }); // p 占有 D p->bar(); } // 调用上述 lambda 并销毁 D std::cout << "Array form of unique_ptr demo\n"; { std::unique_ptr<D[]> p{new D[3]}; } // 调用 ~D 3 次 }
输出:
unique ownership semantics demo D::D D::bar D::bar D::~D Runtime polymorphism demo D::D D::bar D::D D::D D::bar D::bar D::bar D::~D D::~D D::~D Custom deleter demo x Custom lambda-expression deleter demo D::D D::bar destroying from a custom deleter... D::~D Array form of unique_ptr demo D::D D::D D::D D::~D D::~D D::~D
如下对象建模——家长与子女:a Parent has a Child, a Child knowshis/her Parent。
从程序的运行中可以看到最终资源没有得到释放。
一个智能指针在创建一个对象的时候初始化引用计数为 1,并把自己的指针指向创建的对象。但这个引用计数在何处?在智能指针内部?非也,这个计数是一个单独的对象来实现的,如图1,当另外一个智能指针指向这个对象的时候,便找到与这个对象对应的计数对象,并加一个引用,即 use_count++。这样多个智能指针对象便可以使用相同的引用计数。
下面程序中,当指针p释放时,由于指针c->ParentPtr还在引用着new Child,所以这时(new Child)的use_count从2减为1。同理当指针c释放时,由于p->ChildPtr还在引用着new Parent,所以这时(new Parent)的use_count从2减为1。最终,内存没有被释放完全。
class Child; class Parent; class Parent { private: std::shared_ptr<Child> ChildPtr; public: void setChild(std::shared_ptr<Child> child) { this->ChildPtr = child; } void doSomething() { if (this->ChildPtr.use_count()) { } } ~Parent() {} }; class Child { private: std::shared_ptr<Parent> ParentPtr; public: void setPartent(std::shared_ptr<Parent> parent) { this->ParentPtr = parent; } void doSomething() { if (this->ParentPtr.use_count()) { } } ~Child() {} }; int main() { std::weak_ptr<Parent> wpp; std::weak_ptr<Child> wpc; { std::shared_ptr<Parent> p(new Parent); std::shared_ptr<Child> c(new Child); std::cout << "p.use_count() = " << p.use_count() << std::endl; std::cout << "c.use_count() = " << c.use_count() << std::endl; p->setChild(c); c->setPartent(p); std::cout << "p.use_count() = " << p.use_count() << std::endl; std::cout << "c.use_count() = " << c.use_count() << std::endl; wpp = p; wpc = c; std::cout << "p.use_count() = " << p.use_count() << std::endl; // 2 std::cout << "c.use_count() = " << c.use_count() << std::endl; // 2 cout<<endl; } std::cout << "p.use_count() = " << wpp.use_count() << std::endl; // 1 std::cout << "c.use_count() = " << wpc.use_count() << std::endl; // 1 return 0; }
运行结果
p.use_count() = 1 c.use_count() = 1 p.use_count() = 2 c.use_count() = 2 p.use_count() = 2 c.use_count() = 2 p.use_count() = 1 c.use_count() = 1
如下,在两个需要互相引用的类的内部,使用weak_ptr智能指针引用对方,来避免循环引用导致的内存泄漏问题。
#include <iostream> #include <memory> class Child; class Parent; class Parent { private: //std::shared_ptr<Child> ChildPtr; std::weak_ptr<Child> ChildPtr; public: void setChild(std::shared_ptr<Child> child) { this->ChildPtr = child; } void doSomething() { //new shared_ptr if (this->ChildPtr.lock()) { } } ~Parent() { } }; class Child { private: std::shared_ptr<Parent> ParentPtr; public: void setPartent(std::shared_ptr<Parent> parent) { this->ParentPtr = parent; } void doSomething() { if (this->ParentPtr.use_count()) { } } ~Child() { } }; int main() { std::weak_ptr<Parent> wpp; std::weak_ptr<Child> wpc; { std::shared_ptr<Parent> p(new Parent); std::shared_ptr<Child> c(new Child); p->setChild(c); c->setPartent(p); wpp = p; wpc = c; std::cout << p.use_count() << std::endl; // 2 std::cout << c.use_count() << std::endl; // 1 } std::cout << wpp.use_count() << std::endl; // 0 std::cout << wpc.use_count() << std::endl; // 0 return 0; }
运行结果
2100
更多编程资料详见公众号 xutopia77