C++11 智能指针

上一篇线程池里,有一个返回std::shared_ptr<T>对象,他其实是c++里面的智能指针

一、std::shared_ptr

shared_ptr共享被管理对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个shared_ptr对象销毁时,被管理对象自动销毁 

 

std::shared_ptr大概总结有以下几点:

(1) 智能指针主要的用途就是方便资源的管理,自动释放没有指针引用的资源

(2) 使用引用计数来标识是否有多余指针指向该资源。(注意,shart_ptr本身指针会占1个引用)

(3) 在赋值操作中, 原来资源的引用计数会减一,新指向的资源引用计数会加一。

     std::shared_ptr<Test> p1(new Test);

     std::shared_ptr<Test> p2(new Test);

     p1 = p2;

(4) 引用计数加一/减一操作是原子性的,所以线程安全的。

(5) make_shared要优于使用new,make_shared可以一次将需要内存分配好

std::shared_ptr<Test> p = std::make_shared<Test>();
std::shared_ptr<Test> p(new Test);

(6) std::shared_ptr的大小是原始指针的两倍,因为它的内部有一个原始指针指向资源,同时有个指针指向引用计数。

(7) 引用计数是分配在动态分配的,std::shared_ptr支持拷贝,新的指针获可以获取前引用计数个数。

以下是cppreference的代码例子,其实也蛮清楚的

#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";
}
View Code

 

但是有没有可能double free呢

double Free其实就是同一个指针free两次。虽然一般把它叫做double free。其实只要是free一个指向堆内存的指针都有可能产生可以利用的漏洞。

double free的原理其实和堆溢出的原理差不多,都是通过unlink这个双向链表删除的宏来利用的。只是double free需要由自己来伪造整个chunk并且欺骗操作系统

简单来说,shared_ptr实现包含了两部分,

 

  • 一个指向堆上创建的对象的裸指针,raw_ptr

  • 一个指向内部隐藏的、共享的管理对象。share_count_object

 

第一部分没什么好说的,第二部分是需要关注的重点:

 

  • use_count,当前这个堆上对象被多少对象引用了,简单来说就是引用计数。

  • weak_count,这个管理对象被多少个智能指针共享了,简单来说就是管理对象的引用计数。

 

不同指针创建的对同一个堆上对象的智能管理,并不共享管理对象,因此存在double free的可能性

它用于解决 std::shared_ptr 可能引发的循环引用问题。std::weak_ptr 本身并不会增加资源的引用计数,因此不会影响资源的生命周期。它提供了一种安全地访问被 std::shared_ptr 管理的对象的方式,同时避免了循环引用导致的内存泄漏问题。

以下是cppreference的代码例子

#include <iostream>
#include <memory>
 
std::weak_ptr<int> gw;
 
void observe()
{
    std::cout << "gw.use_count() == " << gw.use_count() << "; ";
    // we have to make a copy of shared pointer before usage:
    if (std::shared_ptr<int> spt = gw.lock())
        std::cout << "*spt == " << *spt << '\n';
    else
        std::cout << "gw is expired\n";
}
 
int main()
{
    {
        auto sp = std::make_shared<int>(42);
        gw = sp;
 
        observe();
    }
 
    observe();
}

三、std::unique_ptr

独占所有权的智能指针,每次只能有一个 std::unique_ptr 指向某个对象。当它的生命周期结束时,它所拥有的资源(例如动态分配的内存)会自动释放。

以下是cppreference的代码例子

#include <cassert>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <locale>
#include <memory>
#include <stdexcept>
 
// helper class for runtime polymorphism demo below
struct B
{
    virtual ~B() = default;
 
    virtual void bar() { std::cout << "B::bar\n"; }
};
 
struct D : B
{
    D() { std::cout << "D::D\n"; }
    ~D() { std::cout << "D::~D\n"; }
 
    void bar() override { std::cout << "D::bar\n"; }
};
 
// a function consuming a unique_ptr can take it by value or by rvalue reference
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
    p->bar();
    return p;
}
 
// helper function for the custom deleter demo below
void close_file(std::FILE* fp)
{
    std::fclose(fp);
}
 
// unique_ptr-based linked list demo
struct List
{
    struct Node
    {
        int data;
        std::unique_ptr<Node> next;
    };
 
    std::unique_ptr<Node> head;
 
    ~List()
    {
        // destroy list nodes sequentially in a loop, the default destructor
        // would have invoked its `next`'s destructor recursively, which would
        // cause stack overflow for sufficiently large lists.
        while (head)
        {
            auto next = std::move(head->next);
            head = std::move(next);
        }
    }
 
    void push(int data)
    {
        head = std::unique_ptr<Node>(new Node{data, std::move(head)});
    }
};
 
int main()
{
    std::cout << "1) Unique ownership semantics demo\n";
    {
        // Create a (uniquely owned) resource
        std::unique_ptr<D> p = std::make_unique<D>();
 
        // Transfer ownership to `pass_through`,
        // which in turn transfers ownership back through the return value
        std::unique_ptr<D> q = pass_through(std::move(p));
 
        // p is now in a moved-from 'empty' state, equal to nullptr
        assert(!p);
    }
 
    std::cout << "\n" "2) Runtime polymorphism demo\n";
    {
        // Create a derived resource and point to it via base type
        std::unique_ptr<B> p = std::make_unique<D>();
 
        // Dynamic dispatch works as expected
        p->bar();
    }
 
    std::cout << "\n" "3) Custom deleter demo\n";
    std::ofstream("demo.txt") << 'x'; // prepare the file to read
    {
        using unique_file_t = std::unique_ptr<std::FILE, decltype(&close_file)>;
        unique_file_t fp(std::fopen("demo.txt", "r"), &close_file);
        if (fp)
            std::cout << char(std::fgetc(fp.get())) << '\n';
    } // `close_file()` called here (if `fp` is not null)
 
    std::cout << "\n" "4) Custom lambda-expression deleter and exception safety demo\n";
    try
    {
        std::unique_ptr<D, void(*)(D*)> p(new D, [](D* ptr)
        {
            std::cout << "destroying from a custom deleter...\n";
            delete ptr;
        });
 
        throw std::runtime_error(""); // `p` would leak here if it were a plain pointer
    }
    catch (const std::exception&) { std::cout << "Caught exception\n"; }
 
    std::cout << "\n" "5) Array form of unique_ptr demo\n";
    {
        std::unique_ptr<D[]> p(new D[3]);
    } // `D::~D()` is called 3 times
 
    std::cout << "\n" "6) Linked list demo\n";
    {
        List wall;
        const int enough{1'000'000};
        for (int beer = 0; beer != enough; ++beer)
            wall.push(beer);
 
        std::cout.imbue(std::locale("en_US.UTF-8"));
        std::cout << enough << " bottles of beer on the wall...\n";
    } // destroys all the beers
}

四、std::move

本质上是一种类型转换,将对象从左值引用转换为右值引用,从而可以触发移动构造函数或移动赋值运算符,从而避免不必要的复制

示例

#include <iostream>

int main() {
    int a = 42;

    // 声明一个右值引用,绑定到变量 a
    int&& rref = std::move(a);

    std::cout << "a: " << a << std::endl;       // a 可能已经无效,不建议使用
    std::cout << "rref: " << rref << std::endl;

    rref = 77;

    std::cout << "a: " << a << std::endl;       // a 可能已经无效,不建议使用
    std::cout << "rref: " << rref << std::endl;
    int&& d = rref++;
    std::cout << "rref: " << d << std::endl;
    // int&& e = ++rref; cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’
    return 0;
}

左值引用:type &引用名 = 左值表达式;就是对左值的引用 就是给左值取别名

右值引用:type &&引用名 = 右值表达式;就是对右值的引用 就是给右值取别名

五、boost::intrusive_ptr

自己管理的智能指针,intrusive_ptr 类似shared_ptr,也是一种引用计数型智能指针,但需要额外增加一些代码才能使用它。

#include <boost/intrusive_ptr.hpp>

class MyClass {
public:
    MyClass(int value) : data(value), ref_count(0) {}

    void AddRef() {
        ++ref_count;
    }

    void Release() {
        if (--ref_count == 0) {
            delete this;
        }
    }

    void PrintData() {
        std::cout << "Data: " << data << std::endl;
    }

private:
    int data;
    int ref_count;
};

void intrusive_ptr_add_ref(MyClass* obj) {
    obj->AddRef();
}

void intrusive_ptr_release(MyClass* obj) {
    obj->Release();
}

int main() {
    boost::intrusive_ptr<MyClass> ptr(new MyClass(42));
    ptr->PrintData(); // 调用对象的成员函数
    return 0; // ptr 离开作用域时会自动释放
}
posted @ 2019-07-28 16:43  暴力都不会的蒟蒻  阅读(764)  评论(0编辑  收藏  举报