一文彻底掌握智能指针

发表于 2年以前  | 总阅读数:369 次

前言:

  • 大家好,今天继续分享一篇基础的智能指针的使用,在分享这篇之前,大家可以看之前分享的两种智能指针:[C++智能指针学习(一)] , 今天我们来分享剩下的两个智能指针:
  • std::share_ptr
  • std::weak_ptr
  • 在分享之前,简单回忆一下智能指针的概念:
  • 智能指针:它是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保在离开指针所在作用域时,自动正确的销毁动态内存分配的对象,防止内存泄漏。

std::shared_ptr:

std::unique_ptr 对其持有的资源具有独占性,而 std::shared_ptr 持有的资源可以在多个 std::shared_ptr 之间共享,每多一个 std::shared_ptr 对资源的引用,资源引用计数将增加 1,每一个指向该资源的 std::shared_ptr 对象析构时,资源引用计数减 1,最后一个 std::shared_ptr 对象析构时,发现资源计数为 0,将释放其持有的资源。多个线程之间,递增和减少资源的引用计数是安全的。(注意:这不意味着多个线程同时操作 std::shared_ptr 引用的对象是安全的)。

std::shared_ptr 提供了一个 use_count() 方法来获取当前持有资源的引用计数。除了上面描述的,std::shared_ptr 用法和 std::unique_ptr 基本相同。

下面是一个初始化 std::shared_ptr 的示例:

int main()
{
    //初始化方式1
    std::shared_ptr<int> sp1(new int(123));

    //初始化方式2
    std::shared_ptr<int> sp2;
    sp2.reset(new int(123));

    //初始化方式3
    std::shared_ptr<int> sp3;
    sp3 = std::make_shared<int>(123);

    return 0;
}

和 std::unique_ptr 一样,你应该优先使用 std::make_shared 去初始化一个 std::shared_ptr 对象。

再来看另外一段代码:

#include <iostream>
#include <memory>

class A
{
public:
    A()
    {
        std::cout << "A constructor" << std::endl;
    }

    ~A()
    {
        std::cout << "A destructor" << std::endl;
    }
};

int main()
{
    {
        //初始化方式1
        std::shared_ptr<A> sp1(new A());

        std::cout << "use count: " << sp1.use_count() << std::endl;

        //初始化方式2
        std::shared_ptr<A> sp2(sp1);
        std::cout << "use count: " << sp1.use_count() << std::endl;

        sp2.reset();
        std::cout << "use count: " << sp1.use_count() << std::endl;

        {
            std::shared_ptr<A> sp3 = sp1;
            std::cout << "use count: " << sp1.use_count() << std::endl;
        }

        std::cout << "use count: " << sp1.use_count() << std::endl;
    }

    return 0;
}

所以整个程序的执行结果如下:

A constructor
use count: 1
use count: 2
use count: 1
use count: 2
use count: 1
A destructor

上述代码 22 行 sp1 构造时,同时触发对象 A 的构造,因此 A 的构造函数会执行。

此时只有一个 sp1 对象引用 22 行 new 出来的 A 对象(为了叙述方便,下文统一称之为资源对象 A),因此代码 24 行打印出来的引用计数值为 1。

代码 27 行,利用 sp1 拷贝一份 sp2,导致代码 28 行打印出来的引用计数为 2。

代码 30 行调用 sp2 的 reset() 方法,sp2 释放对资源对象 A 的引用,因此代码 31 行打印的引用计数值再次变为 1。

代码 34 行 利用 sp1 再次 创建 sp3,因此代码 35 行打印的引用计数变为 2。

程序执行到 36 行以后,sp3 出了其作用域被析构,资源 A 的引用计数递减 1,因此 代码 38 行打印的引用计数为 1。

程序执行到 39 行以后,sp1 出了其作用域被析构,在其析构时递减资源 A 的引用计数至 0,并析构资源 A 对象,因此类 A 的析构函数被调用。

1、std::enable_shared_from_this

实际开发中,有时候需要在类中返回包裹当前对象(this)的一个 std::shared_ptr 对象给外部使用,C++ 新标准也为我们考虑到了这一点,有如此需求的类只要继承自 std::enable_shared_from_this 模板对象即可。用法如下:

#include <iostream>
#include <memory>

class A : public std::enable_shared_from_this<A>
{
public:
    A()
    {
        std::cout << "A constructor" << std::endl;
    }

    ~A()
    {
        std::cout << "A destructor" << std::endl;
    }

    std::shared_ptr<A> getSelf()
    {
        return shared_from_this();
    }
};

int main()
{
    std::shared_ptr<A> sp1(new A());

    std::shared_ptr<A> sp2 = sp1->getSelf();

    std::cout << "use count: " << sp1.use_count() << std::endl;

    return 0;
}

上述代码中,类 A 的继承 std::enable_shared_from_this 并提供一个 getSelf() 方法返回自身的 std::shared_ptr 对象,在 getSelf() 中调用 shared_from_this() 即可。

std::enable_shared_from_this 用起来比较方便,但是也存在很多不易察觉的陷阱。

2、陷阱一:不应该共享栈对象的 this 给智能指针对象

假设我们将上面代码 main 函数 25 行生成 A 对象的方式改成一个栈变量,即:

//其他相同代码省略...

int main()
{
    A a;

    std::shared_ptr<A> sp2 = a.getSelf();

    std::cout << "use count: " << sp2.use_count() << std::endl;

    return 0;
}

运行修改后的代码会发现程序在 std::shared_ptr sp2 = a.getSelf() 产生崩溃。这是因为,智能指针管理的是堆对象,栈对象会在函数调用结束后自行销毁,因此不能通过 shared_from_this() 将该对象交由智能指针对象管理。

切记:智能指针最初设计的目的就是为了管理堆对象的(即那些不会自动释放的资源)。

3、陷阱二:避免 std::enable_shared_from_this 的循环引用问题

#include <iostream>
#include <memory>

class A : public std::enable_shared_from_this<A>
{
public:
    A()
    {
        m_i = 9;
        //注意:
        //比较好的做法是在构造函数里面调用shared_from_this()给m_SelfPtr赋值
        //但是很遗憾不能这么做,如果写在构造函数里面程序会直接崩溃

        std::cout << "A constructor" << std::endl;
    }

    ~A()
    {
        m_i = 0;

        std::cout << "A destructor" << std::endl;
    }

    void func()
    {
        m_SelfPtr = shared_from_this();
    }

public:
    int m_i;
    std::shared_ptr<A> m_SelfPtr;
};

int main()
{
    {
        std::shared_ptr<A> spa(new A());
        spa->func();
    }

    return 0;
}

乍一看上面的代码好像看不出什么问题,让我们来实际运行一下看看输出结果:

A constructor

我们发现在程序的整个生命周期内,只有 A 类构造函数的调用输出,没有 A 类析构函数的调用输出,这意味着 new 出来的 A 对象产生了内存泄漏!

我们来分析一下为什么 new 出来的 A 对象得不到释放。当程序执行到 39 行后,spa 出了其作用域准备析构,在析构时其发现仍然有另外的一个 std::shared_ptr 对象即 A::m_SelfPtr 引用了 A,因此 spa 只会将 A 的引用计数递减为 1,然后就销毁自身了。现在留下一个矛盾的处境:必须销毁 A 才能销毁其成员变量 m_SelfPtr,而销毁 m_SelfPtr 必须先销毁 A。这就是所谓的 std::enable_shared_from_this 的循环引用问题。我们在实际开发中应该避免做出这样的逻辑设计,这种情形下即使使用了智能指针也会造成内存泄漏。也就是说一个资源的生命周期可以交给一个智能指针对象,但是该智能指针的生命周期不可以再交给整个资源来管理。

std::weak_ptr:

std::weak_ptr 是一个不控制资源生命周期的智能指针,是对对象的一种弱引用,只是提供了对其管理的资源的一个访问手段,引入它的目的为协助 std::shared_ptr 工作。

std::weak_ptr 可以从一个 std::shared_ptr 或另一个 std::weak_ptr 对象构造,std::shared_ptr 可以直接赋值给 std::weak_ptr ,也可以通过 std::weak_ptr 的 lock() 函数来获得 std::shared_ptr。它的构造和析构不会引起引用计数的增加或减少。std::weak_ptr 可用来解决 std::shared_ptr 相互引用时的死锁问题(即两个std::shared_ptr 相互引用,那么这两个指针的引用计数永远不可能下降为 0, 资源永远不会释放)。

示例代码如下:

#include <iostream>
#include <memory>

int main()
{
    //创建一个std::shared_ptr对象
    std::shared_ptr<int> sp1(new int(123));
    std::cout << "use count: " << sp1.use_count() << std::endl;

    //通过构造函数得到一个std::weak_ptr对象
    std::weak_ptr<int> sp2(sp1);
    std::cout << "use count: " << sp1.use_count() << std::endl;

    //通过赋值运算符得到一个std::weak_ptr对象
    std::weak_ptr<int> sp3 = sp1;
    std::cout << "use count: " << sp1.use_count() << std::endl;

    //通过一个std::weak_ptr对象得到另外一个std::weak_ptr对象
    std::weak_ptr<int> sp4 = sp2;
    std::cout << "use count: " << sp1.use_count() << std::endl;

    return 0;
}

运行结果:

use count: 1
use count: 1
use count: 1
use count: 1

无论通过何种方式创建 std::weak_ptr 都不会增加资源的引用计数,因此每次输出引用计数的值都是 1。

既然,std::weak_ptr 不管理对象的生命周期,那么其引用的对象可能在某个时刻被销毁了,如何得知呢?std::weak_ptr 提供了一个 expired() 方法来做这一项检测,返回 true,说明其引用的资源已经不存在了;返回 false,说明该资源仍然存在,这个时候可以使用 std::weak_ptr 的 lock() 方法得到一个 std::shared_ptr 对象然后继续操作资源,以下代码演示了该用法:

// tmpConn_ 是一个 std::weak_ptr<TcpConnection> 对象
// tmpConn_ 引用的TcpConnection已经销毁,直接返回
if (tmpConn_.expired())
    return;

std::shared_ptr<TcpConnection> conn = tmpConn_.lock();
if (conn)
{
    //对conn进行操作,省略...
}

有读者可能对上述代码产生疑问,既然使用了 std::weak_ptr 的 expired() 方法判断了对象是否存在,为什么不直接使用 std::weak_ptr 对象对引用资源进行操作呢?实际上这是行不通的,std::weak_ptr 类没有重写 operator-> 和 operator* 方法,因此不能像 std::shared_ptr 或 std::unique_ptr 一样直接操作对象,同时 std::weak_ptr 类也没有重写 operator! 操作,因此也不能通过 std::weak_ptr 对象直接判断其引用的资源是否存在:

#include <memory>

class A
{
public:
    void doSomething()
    {
    }
};

int main()
{
    std::shared_ptr<A> sp1(new A());

    std::weak_ptr<A> sp2(sp1);

    //正确代码
    if (sp1)
    {
        //正确代码
        sp1->doSomething();
        (*sp1).doSomething();
    }

    //正确代码
    if (!sp1)
    {
    }

    //错误代码,无法编译通过
    //if (sp2)
    //{
    //    //错误代码,无法编译通过
    //    sp2->doSomething();
    //    (*sp2).doSomething();
    //}

    //错误代码,无法编译通过
    //if (!sp2)
    //{

    //}

    return 0;
}

之所以 std::weak_ptr 不增加引用资源的引用计数不管理资源的生命周期,是因为,即使它实现了以上说的几个方法,调用它们也是不安全的,因为在调用期间,引用的资源可能恰好被销毁了,这会造成棘手的错误和麻烦。

因此,std::weak_ptr 的正确使用场景是那些资源如果可能就使用,如果不可使用则不用的场景,它不参与资源的生命周期管理。例如,网络分层结构中,Session 对象(会话对象)利用 Connection 对象(连接对象)提供的服务工作,但是 Session 对象不管理 Connection 对象的生命周期,Session 管理 Connection 的生命周期是不合理的,因为网络底层出错会导致 Connection 对象被销毁,此时 Session 对象如果强行持有 Connection 对象与事实矛盾。

std::weak_ptr 的应用场景,经典的例子是订阅者模式或者观察者模式中。这里以订阅者为例来说明,消息发布器只有在某个订阅者存在的情况下才会向其发布消息,而不能管理订阅者的生命周期。

class Subscriber
{
};

class SubscribeManager
{
public:
    void publish()
    {
        for (const auto &iter : m_subscribers)
        {
            if (!iter.expired())
            {
                //TODO:给订阅者发送消息
            }
        }
    }

private:
    std::vector<std::weak_ptr<Subscriber>> m_subscribers;
};

智能指针的大小:

一个 std::unique_ptr 对象大小与裸指针大小相同(即 sizeof(std::unique_ptr) == sizeof(void)),而 std::shared_ptr 的大小是 std::unique_ptr 的一倍。以下是我分别在 Visual Studio 2019 和 gcc/g++ 4.8 上(二者都编译成 x64 程序)的测试结果:

#include <iostream>
#include <memory>
#include <string>

int main()
{
    std::shared_ptr<int> sp0;
    std::shared_ptr<std::string> sp1;
    sp1.reset(new std::string());
    std::unique_ptr<int> sp2;
    std::weak_ptr<int> sp3;

    std::cout << "sp0 size: " << sizeof(sp0) << std::endl;
    std::cout << "sp1 size: " << sizeof(sp1) << std::endl;
    std::cout << "sp2 size: " << sizeof(sp2) << std::endl;
    std::cout << "sp3 size: " << sizeof(sp3) << std::endl;

    return 0;
}

Visual Studio 2019 (32bit) 运行结果:

sp0 size:8
sp1 size:8
sp2 size:4
sp3 size:8

gcc/g++ (64bit) 运行结果:

sp0 size:16
sp1 size:16
sp2 size:8
sp3 size:16

在 32 位机器上,std_unique_ptr 占 4 字节,std::shared_ptr 和 std::weak_ptr 占 8 字节。

在 64 位机器上,std_unique_ptr 占 8 字节,std::shared_ptr 和 std::weak_ptr 占 16 字节。

也就是说,std_unique_ptr 的大小总是和原始指针大小一样,std::shared_ptr 和 std::weak_ptr 大小是原始指针的一倍。

智能指针使用注意事项:

C++ 新标准提倡的理念之一是不应该再手动调用 delete 或者 free 函数去释放内存了,而应该把它们交给新标准提供的各种智能指针对象。C++ 新标准中的各种智能指针是如此的实用与强大,在现代 C++ 项目开发中,我们应该尽量去使用它们。智能指针虽然好用,但稍不注意,也可能存在许多难以发现的 bug,这里我根据经验总结了几条:

  • 一旦一个对象使用智能指针管理后,就不该再使用原始裸指针去操作, 看一段代码:
#include <memory>

class Subscriber
{
};

int main()
{
    Subscriber *pSubscriber = new Subscriber();

    std::unique_ptr<Subscriber> spSubscriber(pSubscriber);

    delete pSubscriber;

    return 0;
}

这段代码利用创建了一个堆对象 Subscriber,然后利用智能指针 spSubscriber 去管理之,可以却私下利用原始指针销毁了该对象,这让智能指针对象 spSubscriber 情何以堪啊?

记住,一旦智能指针对象接管了你的资源,所有对资源的操作都应该通过智能指针对象进行,不建议再通过原始指针进行操作了。

当然,除了 std::weak_ptr 之外,std::unique_ptr 和 std::shared_ptr 都提供了获取原始指针的方法——get() 函数。

int main()
{
    Subscriber *pSubscriber = new Subscriber();

    std::unique_ptr<Subscriber> spSubscriber(pSubscriber);

    //pTheSameSubscriber和pSubscriber指向同一个对象
    Subscriber *pTheSameSubscriber = spSubscriber.get();

    return 0;
}
  • 分清楚场合应该使用哪种类型的智能指针:

通常情况下,如果你的资源不需要在其他地方共享,那么应该优先使用 std::unique_ptr,反之使用 std::shared_ptr,当然这是在该智能指针需要管理资源的生命周期的情况下;如果不需要管理对象的生命周期,请使用 std::weak_ptr。

  • 认真考虑,避免操作某个引用资源已经释放的智能指针

前面的例子,一定让你觉得非常容易知道一个智能指针的持有的资源是否还有效,但是还是建议在不同场景谨慎一点,有些场景是很容易造成误判。例如下面的代码:

#include <iostream>
#include <memory>

class T
{
public:
    void doSomething()
    {
        std::cout << "T do something..." << m_i << std::endl;
    }

private:
    int m_i;
};

int main()
{
    std::shared_ptr<T> sp1(new T());
    const auto &sp2 = sp1;

    sp1.reset();

    //由于sp2已经不再持有对象的引用,程序会在这里出现意外的行为
    sp2->doSomething();

    return 0;
}

上述代码中,sp2 是 sp1 的引用,sp1 被置空后,sp2 也一同为空。这时候调用 sp2->doSomething(),sp2->(即 operator->)在内部会调用 get() 方法获取原始指针对象,这时会得到一个空指针(地址为 0),继续调用 doSomething() 导致程序崩溃。

你一定仍然觉得这个例子也能很明显地看出问题,ok,让我们把这个例子放到实际开发中再来看一下:

//连接断开
void MonitorServer::OnClose(const std::shared_ptr<TcpConnection> &conn)
{
    std::lock_guard<std::mutex> guard(m_sessionMutex);
    for (auto iter = m_sessions.begin(); iter != m_sessions.end(); ++iter)
    {
        //通过比对connection对象找到对应的session
        if ((*iter)->GetConnectionPtr() == conn)
        {
            m_sessions.erase(iter);
            //注意这里:程序在此处崩溃
            LOGI("monitor client disconnected: %s", conn->peerAddress().toIpPort().c_str());
            break;
        }
    }
}

该段程序会在代码 12 行处崩溃,崩溃原因是调用了 conn->peerAddress() 方法。为什么这个方法的调用可能会引起崩溃?现在可以一目了然地看出了吗?

崩溃原因是传入的 conn 对象和上一个例子中的 sp2 一样都是另外一个 std::shared_ptr 的引用,当连接断开时,对应的 TcpConnection 对象可能早已被销毁,而 conn 引用就会变成空指针(严格来说是不再拥有一个 TcpConnection 对象),此时调用 TcpConnection 的 peerAddress() 方法就会产生和上一个示例一样的错误。

  • 作为类成员变量时,应该优先使用前置声明(forward declarations)

我们知道,为了减小编译依赖加快编译速度和生成二进制文件的大小,C/C++ 项目中一般在 *.h 文件对于指针类型尽量使用前置声明,而不是直接包含对应类的头文件。例如:

//Test.h
//在这里使用A的前置声明,而不是直接包含A.h文件
class A;

class Test
{
public:
    Test();
    ~Test();

private:
    A *m_pA;
};

同样的道理,在头文件中当使用智能指针对象作为类成员变量时,也应该优先使用前置声明去引用智能指针对象的包裹类,而不是直接包含包含类的头文件。

//Test.h
#include <memory>

//智能指针包裹类A,这里优先使用A的前置声明,而不是直接包含A.h
class A;

class Test
{
public:
    Test();
    ~Test();

private:
    std::unique_ptr<A> m_spA;
};

Modern C/C++ 已经变为 C/C++ 开发的趋势,希望能善用和熟练这些智能指针对象。

智能指针的简单实现:

最后,给出智能指针的简单实现,因为 weak_ptr 作为弱引用指针,其实现依赖于 Counter 计数器类和 shared_ptr 的赋值,所以先进行 Counter 计数器类和 share_ptr 的简单实现。

  • Counter的简单实现:
/*
 * 计数器
 * Counter对象就是用来申请一块内存存储引用计数
 * m_refCount是SharedPtr的引用计数
 * m_weakCount是WeakPtr的引用计数
 * 当m_weakCount为0时删除Counter对象
 */
template <typename T>
class Counter
{
    friend class SharedPtr<T>;
    friend class WeakPtr<T>;

public:
    Counter() : m_refCount(0), m_weakCount(0) {}
    virtual ~Counter() {}

private:
    Counter(const Counter &) = delete;
    Counter &operator=(const Counter &) = delete;

private:
    atomic_uint m_refCount;  // #shared,原子操作
    atomic_uint m_weakCount; // #weak,原子操作
};
  • shared_ptr的简单实现:
/*
 * SharedPtr的简单实现
 */
template <typename T>
class SharedPtr
{

    friend class WeakPtr<T>;

public:
    /*
     * 构造函数,用原生指针构造
     */
    SharedPtr(T *ptr) : m_ptr(ptr), m_cnt(new Counter<T>)
    {
        if (ptr)
        {
            m_cnt->m_refCount = 1;
        }
        cout << "Ptr Construct S." << endl;
    }

    ~SharedPtr()
    {
        release();
    }
    /*
     * 拷贝构造函数,用另一个SharedPtr对象构造
     */
    SharedPtr(const SharedPtr &s)
    {
        m_ptr = s.m_ptr;
        s.m_cnt->m_refCount++;
        m_cnt = s.m_cnt;
        cout << "S Copy Construct S." << endl;
    }

    /*
     * 拷贝构造函数,用另一个WeakPtr对象构造
     * 为了WeakPtr对象调用自己的lock()方法将自己传进来构造一个SharedPtr返回
     */
    SharedPtr(const WeakPtr<T> &w)
    {
        m_ptr = w.m_ptr;
        w.m_cnt->m_refCount++;
        m_cnt = w.m_cnt;
        cout << "W Copy Construct S." << endl;
    }

    /*
     * 赋值构造函数,用另一个SharedPtr对象构造
     */
    SharedPtr<T> &operator=(const SharedPtr<T> &s)
    {
        if (this != s)
        {
            this->release();
            m_ptr = s.m_ptr;
            s.m_cnt->m_refCount++;
            m_cnt = s.m_cnt;
            cout << "S Assign Construct S." << endl;
        }
        return *this;
    }

    T &operator*()
    {
        return *m_ptr;
    }

    T *operator->()
    {
        return m_ptr;
    }

protected:
    void release()
    {
        m_cnt->m_refCount--;
        if (m_cnt->m_refCount < 1)
        {
            delete m_ptr;
            m_ptr = nullptr;
            cout << "SharedPtr Delete Ptr." << endl;
            if (m_cnt->m_weakCount < 1)
            {
                delete m_cnt;
                m_cnt = nullptr;
                cout << "SharedPtr Delete Cnt." << endl;
            }
            cout << "SharedPtr Release." << endl;
        }
    }

private:
    T *m_ptr;
    Counter<T> *m_cnt;
};
  • weak_ptr的简单实现:
template <typename T>
class WeakPtr
{
public:
    /*
     * 构造函数,用SharedPtr对象构造
     */
    WeakPtr(SharedPtr<T> &s) : m_ptr(s.m_ptr), m_cnt(s.m_cnt)
    {
        m_cnt->m_weakCount++;
        cout << "S Construct W." << endl;
    }
    /*
     * 构造函数,用WeakPtr对象构造
     */
    WeakPtr(WeakPtr<T> &w) : m_ptr(w.m_ptr), m_cnt(w.m_cnt)
    {
        m_cnt->m_weakCount++;
        cout << "W Construct W." << endl;
    }

    ~WeakPtr()
    {
        release();
    }
    /*
     * 赋值构造函数,用另一个SharedPtr对象构造
     */
    WeakPtr<T> &operator=(SharedPtr<T> &s)
    {
        release();
        m_cnt = s.m_cnt;
        m_cnt->m_weakCount++;
        m_ptr = s.m_ptr;
        cout << "S Assign Construct W." << endl;
        return *this;
    }
    /*
     * 赋值构造函数,用另一个WeakPtr对象构造
     */
    WeakPtr<T> &operator=(WeakPtr<T> &w)
    {
        if (this != &w)
        {
            release();
            m_cnt = w.m_cnt;
            m_cnt->m_weakCount++;
            m_ptr = w->m_ptr;
            cout << "W Assign Construct W." << endl;
        }
        return *this;
    }
    /*
     * WeakPtr通过lock函数获得SharedPtr
     */
    SharedPtr<T> &lock()
    {
        return SharedPtr<T>(*this);
    }
    /*
     * 检查SharedPtr是否已过期
     */
    bool expired()
    {
        if (m_cnt)
        {
            if (m_cnt->m_refCount > 0)
                return false;
        }
        return true;
    }

private:
    WeakPtr() = delete;       WeakPtr禁止默认构造,只能从SharedPtr或者WeakPtr构造
    T &operator*() = delete;  //WeakPtr禁止*
    T *operator->() = delete; //WeakPtr禁止->

private:
    void release()
    {
        if (m_cnt)
        {
            m_cnt->m_weakCount--;
            if (m_cnt->m_weakCount < 1 && m_cnt->m_refCount < 1)
            {
                delete m_cnt;
                m_cnt = nullptr;
                cout << "Delete Cnt." << endl;
            }
            cout << "WeakPtr Release." << endl;
        }
    }

private:
    T *m_ptr;
    Counter<T> *m_cnt;
};

上面的实现可能不是非常严谨,仅实现了常用的的函数接口而已,但其主要的目的是为了更深刻的了解智能指针的原理,这样才能更有把握的使用智能指针,只有了解它的内部实现,对于使用中的一些坑才能有效避免。

文章相关参考:https://blog.csdn.net/code_peak/article/details/119722167

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/UmIoCxfkZwmIFL1pSSJynQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237270次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8108次阅读
 目录