智能指针

unique_ptr

为什么引入智能指针unique_ptr

普通指针的不足
  1. 内存泄漏:最常见的00问题是忘记释放分配给指针的内存,导致内存泄漏。这通常发生在异常情况或程序员忘记在不再需要内存时释放它。
  2. 悬空指针:当对象被删除,但指针未被重置时,指针就悬空了。悬空指针可能会指向无效的内存区域,尝试访问可能会导致未定义的行为。
  3. 所有权不明:当指针被传递给另一个函数或线程时,很难跟踪谁拥有该指针,并负责最终的删除。这种所有权不明确常导致多重释放或非预期的内存泄漏。
  4. 资源管理困难:对于非内存资源(如文件句柄、网络连接等),使用普通指针进行管理同样需要程序员手动关闭或释放资源,这增加了出错的可能性。
普通指针的释放
  1. 类内的指针:在析构函数中释放。
  2. C++内置数据类型,如何释放?
  3. new出来的类,本身如何释放?
智能指针的设计思路

智能指针的核心设计思路是利用对象的生命周期来管理资源(特别是内存),从而避免资源泄露和其他相关问题。

  1. 智能指针是类模板,在栈上创建智能指针对象
    智能指针在本质上是模板类,意味着它们可以用于指向任何类型的对象。
  2. 把普通指针交给智能指针管理
    当你创建了一个原始指针后,你可以将其交给一个智能指针来管理。这样做的好处是,智能指针会负责这个原始指针所指向的内存的释放工作。
  3. 智能指针对象过期时,调用析构函数释放普通指针的内存
    由于智能指针是在栈上分配的,因此当智能指针的生命周期结束(例如,当它超出作用域时)时,它的析构函数会自动被调用。在智能指针的析构函数中,它会释放其管理的原始指针的内存。

unique_ptr 使用

unique_ptr提供了严格的所有权管理机制,通过独占的方式管理其指向的对象。也就是说unique_ptr拥有其指向的对象,确保同一时间只有一个unique_ptr拥有该对象。当这个unique_ptr被销毁(例如,超出作用域)时,指向的对象也随即被销毁。

使用unique_ptr需要包含头文件#include<memory>

unique_ptr的结构

#include <memory>template <typename T, typename D = std::default_delete<T>>
class unique_ptr {
public:// 基础构造函数,接受一个指向已分配对象的指针。explicit unique_ptr(T* p = nullptr) noexcept : ptr(p) {}// 析构函数,负责释放 unique_ptr 拥有的对象。~unique_ptr() {if (ptr) {D deleter;deleter(ptr); // 默认使用 delete 删除器}}// 移动构造函数,接受一个右值引用至其他 unique_ptr。// “窃取”资源,将源 unique_ptr 置为空状态。unique_ptr(unique_ptr&& other) noexcept : ptr(other.ptr) {other.ptr = nullptr;}// 移动赋值操作符,接受一个右值引用至其他 unique_ptr。// 释放当前对象,接管新对象的资源,将源 unique_ptr 置为空状态。unique_ptr& operator=(unique_ptr&& other) noexcept {if (this != &other) {reset(); // 释放当前指针ptr = other.ptr; // 接管资源other.ptr = nullptr; // 将源 unique_ptr 置空}return *this;}// 禁用拷贝构造函数unique_ptr(const unique_ptr&) = delete;// 禁用拷贝赋值操作符unique_ptr& operator=(const unique_ptr&) = delete;// 解引用操作符,返回所指对象的引用。T& operator*() const {return *ptr;}// 箭头操作符,允许通过 unique_ptr 访问其所拥有对象的成员。T* operator->() const noexcept {return ptr;}// 释放 unique_ptr 对象的所有权,返回原始指针,unique_ptr 不再负责对象的删除。T* release() noexcept {T* old_ptr = ptr;ptr = nullptr;return old_ptr;}// 释放 unique_ptr 当前拥有的对象,并可选地接受一个新的对象指针。void reset(T* p = nullptr) noexcept {if (ptr) {D deleter;deleter(ptr); // 删除当前对象}ptr = p; // 接受新的对象指针(如果提供的话)}private:T* ptr; // 内部原始指针,指向 unique_ptr 所拥有的对象。
};
  • 第一个模板参数 T 是指针指向的对象类型。
  • 第二个模板参数 D 是用于删除所拥有对象的策略,即删除器。默认情况下,使用的是 default_delete,它会用 delete 操作符来删除对象。

基本用法

测试类AA的定义:

class AA
{
public:string m_name;AA() { cout << m_name << "调用构造函数AA()。\n"; }AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }~AA() { cout << m_name << "调用了析构函数~AA(" << m_name << ")。\n"; }
};
1. 初始化

方法一:直接初始化

unique_ptr<AA> p1(new AA("天梦"));

方法二:使用 std::make_unique(C++14 及以后版本推荐使用)

auto p1 = make_unique<AA>("天梦");   // 使用 make_unique (类型推断)
unique_ptr<AA> p2 = make_unique<AA>("天梦");  // 明确指定类型

方法三:使用已存在的指针(不推荐)

AA* p = new AA("西施");
unique_ptr<AA> p0(p);  // 用已存在的地址初始化,这种方式不推荐因为它可能导致所有权不明确。
2. 使用方法

unique_ptr 重载了解引用操作符 * 和箭头操作符 ->,这意味着它们可以像普通指针一样使用,但它们提供了额外的内存管理功能。

auto p = make_unique<AA>("天梦");
p->method();  // 调用对象的方法
(*p).method();  // 同上

不支持拷贝构造和拷贝赋值,因为 unique_ptr 的设计目的是提供对其所指对象的唯一所有权。

下面具体解释一下为什么unique_ptr不支持拷贝构造和拷贝赋值。

我认为unique_ptr它核心是提供了对动态分配内存的“独占所有权”。这种所有权模型具有严格限制,为了避免资源泄露和与其他资源共享相关的问题。

  1. 防止资源的多重删除:如果说unique_ptr能够被复制,那么你最终会得到多个智能指针实例指向同一个资源。这本身并不坏,但问题在于每个实例可能都认为自己“拥有”资源,因此,当这些实例中其中一个离开作用域销毁其拥有的资源后,会导致其他指向同一资源的unique_ptr实例成为野指针,他们也可能尝试删除已经不复存在的资源,从而导致未定义行为,通常是程序崩溃。
  2. 强化唯一所有权语义unique_ptr 的核心思想是它是资源的唯一所有者。允许复制会破坏这种唯一性,因为复制的结果是有两个所有者指向同一个资源。通过禁止复制,unique_ptr 确保了所有权的概念始终得到维护,你可以明确地知道资源的生命周期和所有者身份。
  3. 异常安全:使用原始指针的代码通常对异常情况的处理不够,可能会导致资源泄漏。例如,如果在执行复制操作的过程中抛出异常,程序可能会处于一种状态,其中某些已分配资源没有被清理。而 unique_ptr 通过确保资源只有一个所有者来避免这种情况,即使在异常情况下,也会在 unique_ptr 对象销毁时释放资源。
AA* p = new AA("天梦");unique_ptr<AA> pu2 = p;              // 错误,不能把普通指针直接赋给智能指针。unique_ptr<AA> pu3 = new AA("天梦"); // 错误,不能把普通指针直接赋给智能指针。unique_ptr<AA> pu2 = pu1;           // 错误,不能用其它unique_ptr拷贝构造。unique_ptr<AA> pu3;pu3 = pu1;                            // 错误,不能用=对unique_ptr进行赋值。

但是,你可以通过移动语义转移所有权:

unique_ptr<AA> p1 = make_unique<AA>("天梦");
unique_ptr<AA> p2 = std::move(p1); // p1 释放所有权,p2 成为新的所有者
  • 注意

  1. 不要用同一个原始指针(普通指针、裸指针)初始化多个unique_ptr对象。
  2. get()方法返回裸指针。
  3. 不要用unique_ptr管理不是new分配的内存。
  4. 不支持指针算术(即,不支持 +, -, ++, --)。

请看简单示例:

#include <iostream>
#include <memory>
using namespace std;class AA
{
public:string m_name;AA() { cout << m_name << "调用构造函数AA()。\n"; }AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }~AA() { cout << m_name << "调用了析构函数~AA(" << m_name << ")。\n"; }
};int main() {AA* p = new AA("天梦");  // 定义原始指针p,分配内存并构造AA对象。unique_ptr<AA> pu(p);  // 创建智能指针对象,接管原始指针p的内存管理权。cout << "裸指针的值是: " << p << endl;  // 显示原始指针的内存地址。cout << "pu输出的结果是: " << pu.get() << endl;  // 显示智能指针管理的对象的内存地址。cout << "pu.get() 输出的结果是: " << pu.get() << endl;  // 同上,显示智能指针中存储的原始指针地址。cout << "pu的地址是: " << &pu << endl;  // 显示智能指针自身的地址。return 0;
}

其中pu输出的结果是智能指针管理的对象的内存地址。这是因为unique_ptr重载了operator<<,返回的是unique_ptr所指向的对象的内存地址,就像它是一个裸指针一样。

3. 用于函数的参数

unique_ptr 作为函数参数时,通常传递引用或者通过 std::move 转移所有权,因为它们不支持拷贝语义。

void some_function(unique_ptr<AA>& ptr) {// 不会转移所有权的函数
}void some_function_take_ownership(unique_ptr<AA> ptr) {// 接受所有权的函数
}// 调用
auto p = make_unique<AA>("天梦");
some_function(p);  // 传递引用,p 仍然拥有对象
some_function_take_ownership(std::move(p));  // 转移所有权,p 不再拥有对象

unique_ptr的使用技巧

  1. 将一个unique_ptr赋给另一个时,如果源unique_ptr是一个临时右值,编译器允许这样做。如果源unique_ptr将存在一段时间,编译器禁止这样做。一般用于函数的返回值。

    unique_ptr<AA> p0;
    p0 = unique_ptr<AA>(new AA ("天梦")); //使用匿名对象赋值。
    

    我们使用移动赋值操作符将临时 unique_ptr 的所有权赋给 p0。在这个操作完成后,p0 现在拥有这个 AA 对象,而临时的 unique_ptr 不再拥有任何对象。

  2. nullptrunique_ptr赋值将释放对象,空的unique_ptr == nullptr

       unique_ptr<AA> p (new AA("天梦"));if(p != nullptr) cout<< "p is not null" << endl;p = nullptr;if(p == nullptr) cout<< "p is null" << endl;
    

    如果你使用 nullptrstd::unique_ptr 赋值,它将释放其当前所拥有的对象(如果有的话),并将内部指针设置为 nullptr。这可以被用作一种释放 unique_ptr 所拥有的资源的方法。

  3. release()释放对原始指针的控制权,将unique_ptr置为空,返回裸指针(可以用于把unique_ptr传递给子函数,子函数将负责释放对象)

  4. std::move()可以转移对原始指针的控制权。(可用于把unique_ptr传递给子函数,子函数形参也是unique_ptr)

    #include <iostream>
    #include <memory>
    using  namespace std;class AA
    {
    public:string m_name;AA() { cout << m_name << "调用构造函数AA()。\n"; }AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
    };// 函数func1()需要一个指针,但不对这个指针负责。
    void func1(const AA* a) {cout << a->m_name << endl;
    }// 函数func2()需要一个指针,并且会对这个指针负责。
    void func2(AA* a) {cout << a->m_name << endl;delete a;
    }// 函数func3()需要一个unique_ptr,不会对这个unique_ptr负责。
    void func3(const unique_ptr<AA> &a) {cout << a->m_name << endl;
    }// 函数func4()需要一个unique_ptr,并且会对这个unique_ptr负责。
    void func4(unique_ptr<AA> a) {cout << a->m_name << endl;
    }int main()
    {unique_ptr<AA> pu(new AA("tianmeng"));cout << "开始调用函数。\n";//func1(pu.get());        // 函数func1()需要一个指针,但不对这个指针负责。//func2(pu.release());  // 函数func2()需要一个指针,并且会对这个指针负责。//func3(pu);                // 函数func3()需要一个unique_ptr,不会对这个unique_ptr负责。func4(move(pu));     // 函数func4()需要一个unique_ptr,并且会对这个unique_ptr负责。cout << "调用函数完成。\n";if (pu == nullptr) cout << "pu是空指针。\n";
    }
    
  5. reset()释放对象。

    void reset(T * _ptr= (T *) nullptr);
    pp.reset();        // 释放pp对象指向的资源对象。
    pp.reset(nullptr);  // 释放pp对象指向的资源对象
    pp.reset(new AA("bbb"));  // 释放pp指向的资源对象,同时指向新的对象。
    
  6. swap()交换两个unique_ptr的控制权

    pp1.swap(pp2); // 交换两个 unique_ptr 的所有权
    
  7. unique_ptr也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样。

    #include <iostream>
    #include <memory>
    using namespace std;class Base {
    public:virtual ~Base() = default;virtual void show() const {cout << "Base class" << endl;}
    };class Derived : public Base {
    public:void show() const override {cout << "Derived class" << endl;}
    };int main() {unique_ptr<Base> basePtr = make_unique<Derived>();basePtr->show();  // Outputs: Derived classreturn 0;
    }
    
  8. unique_ptr不是绝对安全,如果程序中调用exit()退出,全局的unique_ptr可以自动释放,但局部的unique_ptr无法释放。

    #include <iostream>
    #include <memory>
    using  namespace std;class AA
    {
    public:string m_name;AA() { cout << m_name << "调用构造函数AA()。\n"; }AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
    };unique_ptr<AA> p (new AA("全局变量"));int main(){unique_ptr<AA> p1(new AA("局部变量"));exit(0);
    }
    
  9. unique_ptr提供了支持数组的具体化版本。

    数组版本的unique_ptr,重载了操作符[],操作符[]返回的是引用,可以作为左值使用。
    声明unique_ptr管理的数组:

    unique_ptr<int[]> myArray(new int[5]);
    

    或者使用现代的 std::make_unique 函数(从 C++14 开始支持数组版本):

    auto myArray = std::make_unique<int[]>(5);
    

    与裸指针数组类似,你可以使用 [] 操作符来访问数组中的每个元素。由于返回的是引用,你可以用它作为左值

shared_ptr

shared_ptr共享它所指向的对象,多个shared_ptr可以指向(关联)相同的的对象,并且在内部采用计数机制来实现。

当新的shared_ptr与对象关联时,引用计数加一。

shared_ptr超出作用域时,引用计数减1。当引用计数变为0时,则表示没有任何shared_ptr与对象关联,则释放该对象。

基本用法

初始化
  1. 方法一:直接使用构造函数

    shared_ptr<AA> p0(new AA("西施"));
    
  2. 方法二:使用mark_shared

    shared_ptr<AA> p0 = make_shared<AA>("天梦");  // C++11标准,效率更高。
    shared_ptr<int> pp1=make_shared<int>();         // 数据类型为int。
    shared_ptr<AA> pp2 = make_shared<AA>();       // 数据类型为AA,默认构造函数。
    shared_ptr<AA> pp3 = make_shared<AA>("天梦");  // 数据类型为AA,一个参数的构造函数。
    shared_ptr<AA> pp4 = make_shared<AA>("天梦",8); // 数据类型为AA,两个参数的构造函数。
    
  3. 方法三:采用已有的裸指针

    AA* p1 = new AA("天梦");
    shared_ptr<AA> p2(p1);
    
  4. 方法四:复制构造和赋值函数

    shared_ptr<AA> p3 = p0;  // 使用赋值操作符
    shared_ptr<AA> p4(p0);  // 使用复制构造函数
    
使用方法
  • 操作符重载shared_ptr 重载了 *->,这意味着你可以像使用裸指针一样使用它。
  • 引用计数:你可以使用 use_count() 方法来查询当前有多少个 shared_ptr 共享同一个对象。而 unique() 方法则检查是否只有一个 shared_ptr 指向该对象。
  • 赋值操作shared_ptr支持赋值,左值的shared_ptr的计数器将减1,右值shared_ptr的计算器将加1。
  • 获取裸指针get() 方法可以返回内部的裸指针,但通常应该避免使用它,除非真的需要裸指针。
  • 注意事项:不应该使用同一个裸指针来初始化多个独立的 shared_ptr,这会导致在程序结束时该对象被多次删除,从而引起未定义的行为。
用于函数的参数

unique_ptr相同

shared_ptr的使用技巧

  1. nullptrshared_ptr赋值将把计数减1,如果计数为0,将释放对象,空的shared_ptr==nullptr
  2. std::move()可以转移对原始指针的控制权。还可以将unique_ptr转移成shared_ptr
  3. reset()改变与资源的关联关系。
  4. swap()交换两个shared_ptr的控制权。
  5. shared_ptr也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样。
  6. shared_ptr不是绝对安全,如果程序中调用exit()退出,全局的shared_ptr可以自动释放,但局部的shared_ptr无法释放。
  7. shared_ptr的引用计数是线程安全的,但在多线程环境中修改shared_ptr或其所指向的对象时仍需要加锁。
  8. 如果unique_ptr足以解决问题,那么优先选择unique_ptr。因为unique_ptrshared_ptr更高效,使用的资源也更少。

智能指针的删除器

在C++中,尽管智能指针可以自动删除所指向的对象,但在某些情况下,可能需要以特定的方式删除这些对象,例如,当对象是在非标准堆上分配的,或者与其他资源(如文件句柄或数据库连接)相关联时。这时,自定义删除器就派上了用场。

删除器是什么

默认情况下,智能指针,如shared_ptrunique_ptr,在其生命周期结束时使用delete操作符来释放其管理的对象。但是,你可以提供一个自定义的删除器,以便更改智能指针的行为。

删除器可以是

  • 全局函数:这是一个简单的函数,接受一个原始指针参数,并执行所需的清理操作。
  • 仿函数(Functor):这是一个定义了operator()的类或结构。它可以保持状态,并可以作为删除器使用。
  • Lambda表达式:这是一个简短的、可内联的匿名函数。它非常适合作为简短的删除器。

注意:智能指针使用删除器时,只要在模板签名里指定删除器的类型。

如:

unique_ptr <AA,*decltype*(deletefunc)*> p4(*new* AA("p4"),deletefunc);  *//* *普通函数*

下面是详细示例:

#include <iostream>
#include <vector>
#include <memory>
#include <cmath>using namespace std;class AA
{
public:string m_name;AA() { cout << m_name << "调用构造函数AA()。\n"; }AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
};void deletefunc(AA *a){  // 自定义删除器   普通函数cout<<"自定义删除器(全局函数)"<<endl;delete a;
}struct deleteclass{    // 自定义删除器 仿函数void operator()(AA *a){cout<<"自定义删除器(仿函数)"<<endl;delete a;}
};auto deletelambda = [](AA *a){  // 自定义删除器 lambda表达式cout<<"自定义删除器(lambda表达式)"<<endl;delete a;
};int main(){shared_ptr <AA> p1(new AA("p1"),deletefunc);  // 普通函数shared_ptr <AA> p2(new AA("p2"),deleteclass());  // 仿函数shared_ptr <AA> p3(new AA("p3"),deletelambda);  // lambda表达式unique_ptr <AA,decltype(deletefunc)*> p4(new AA("p4"),deletefunc);  // 普通函数unique_ptr <AA,void (*)(AA *)> p5(new AA("p5"),deletefunc);  // 普通函数unique_ptr <AA,deleteclass> p6(new AA("p6"));  // 仿函数unique_ptr <AA,decltype(deletelambda)> p7(new AA("p7"),deletelambda);  // lambda表达式}

weak_ptr

shared_ptr存在的问题

首先,我们要明确shared_ptr的主要优点:自动引用计数。这意味着每当有一个新的shared_ptr指向同一个资源,这个资源的引用计数会增加,当某个shared_ptr不再存在,计数会减少。只有当引用计数为0时,资源才会被释放。然而,这种机制有一个重要的弱点,就是循环引用。

循环引用

循环引用发生在两个或多个shared_ptr对象相互引用,导致他们之间形成了一个引用环。在这种情况下,涉及到的所有对象的引用计数都不会将至为0,这就意味着这些对象永远不会被自动删除,从而导致内存泄漏。

#include <iostream>
#include <memory>
using namespace std;class B;class A {
public:shared_ptr<B> b_ptr;~A() {cout << "A destructor called!" << endl;}
};class B {
public:shared_ptr<A> a_ptr;~B() {cout << "B destructor called!" << endl;}
};int main() {{shared_ptr<A> a = make_shared<A>();shared_ptr<B> b = make_shared<B>();a->b_ptr = b;  // A对象引用B对象b->a_ptr = a;  // B对象引用A对象}  // a和b的析构函数都不会被调用,因为存在循环引用cout << "End of main()" << endl;
}

在上述示例中,我们在A和B两个类中,分别创建了一个shared_ptr指向另一个类的对象,并在main函数中创建A和B的shared_ptr实例,互相引用。

由于A的实例持有指向Bshared_ptr,并且B的实例持有指向Ashared_ptr,所以两者形成了一个引用循环。当我们退出作用域后,尽管ab的生命周期已经结束,但由于存在循环引用,它们的引用计数都不为0,因此析构函数不会被调用,从而导致内存泄漏。

weak_ptr是什么

为了解决shared_ptr可能导致的循环引用问题,C++11引入了weak_ptr,与shared_ptr不同,weak_ptr不会增加或减少其所观察对象的引用计数。这意味着weak_ptr可以观察一个对象,但不会导致该对象的生命期延长或缩短。

功能和用途

  • weak_ptr主要与shared_ptr一起使用,以观察shared_ptr所拥有的资源。
  • weak_ptr观察的资源被销毁(例如,所有的shared_ptr都被销毁了)时,weak_ptr可以检测到这一点。
  • weak_ptrshared_ptr的一个"安全伙伴",使开发人员能够安全地管理复杂的对象关系,避免循环引用导致的内存泄漏。

如何使用weak_ptr

由于weak_ptr没有重载的->*操作符,您不能直接通过weak_ptr来访问其观察的对象。但您可以通过以下方式与weak_ptr互动:

  1. operator=():此操作允许您将一个shared_ptr或另一个weak_ptr赋值给当前的weak_ptr。这样,weak_ptr就可以观察(但不共享)该资源。
  2. expired():此函数用于检查weak_ptr指向的资源是否仍然存在。如果资源已经被销毁(即与之相关的所有shared_ptr都已经被销毁),则expired()返回true
  3. lock():这可能是weak_ptr中最常用的函数。它尝试获取指向对象的shared_ptr。如果对象仍然存在(即其引用计数不为0),lock()返回一个有效的shared_ptr。否则,它返回一个空的shared_ptr。这是一个线程安全的操作。
  4. reset():这将使weak_ptr不再指向任何对象,即它将其内部指针置为空。
  5. swap():此函数交换两个weak_ptr对象的内容。

使用weak_ptr的常见场景

  • 解决循环引用:如果两个对象互相引用并且使用shared_ptr,可能会出现循环引用的问题。在这种情况下,您可以使用weak_ptr来打破这种循环。
  • 缓存和池:如果您正在实现某种资源池或缓存,并希望知道某资源是否仍在使用(但不想阻止其被删除),weak_ptr是一个好选择。
  • 观察者模式:当对象需要被多个观察者观察,但不应该由观察者保持活跃时,可以使用weak_ptr

下面是一个简单示例:

#include <iostream>
#include <memory>using namespace std;class Sample {
public:Sample() {cout << "Sample constructor called!" << endl;}~Sample() {cout << "Sample destructor called!" << endl;}
};int main() {// 使用 shared_ptr 创建一个 Sample 对象shared_ptr<Sample> sp1 = make_shared<Sample>();// 使用 weak_ptr 指向该 Sample 对象weak_ptr<Sample> wp1;// 1. operator=()wp1 = sp1; // 使用 operator=() 将 shared_ptr 赋值给 weak_ptr// 使用 weak_ptr 指向同一个 Sample 对象weak_ptr<Sample> wp2 = wp1;// 2. expired()if (wp1.expired()) {cout << "wp1 is expired." << endl;} else {cout << "wp1 is not expired." << endl;}// 3. lock()shared_ptr<Sample> sp2 = wp1.lock(); // 提升 weak_ptr 为 shared_ptrif (sp2) {cout << "Successfully locked wp1!" << endl;}// 释放 sp1 指向的资源sp1.reset();if (wp1.expired()) {cout << "wp1 is now expired." << endl;}// 4. reset()wp2.reset(); // 将 wp2 置为空if (wp2.expired()) {cout << "wp2 is expired." << endl;}// 5. swap()wp1.swap(wp2); // 交换 wp1 和 wp2 的内容if (wp2.expired()) {cout << "After swap, wp2 is still expired." << endl;}if (wp1.expired()) {cout << "After swap, wp1 is now expired." << endl;}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/114605.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】:类和对象(中)之拷贝构造函数+赋值运算符重载

拷贝构造函数 概念 在现实生活中&#xff0c;可能存在一个与你一样的自己&#xff0c;我们称其为双胞胎 那在创建对象时&#xff0c;可否创建一个与已存在对象一某一样的新对象呢&#xff1f; 拷贝构造函数&#xff1a;只有单个形参&#xff0c;该形参是对本类类型对象的引用…

怎么让英文大语言模型支持中文?--构建中文tokenization--继续预训练--指令微调

1 构建中文tokenization 参考链接&#xff1a;https://zhuanlan.zhihu.com/p/639144223 1.1 为什么需要 构建中文tokenization&#xff1f; 原始的llama模型对中文的支持不太友好&#xff0c;接下来本文将讲解如何去扩充vocab里面的词以对中文进行token化。 1.2 如何对 原始数…

单独使用logback作为日志

文章目录 引入logback的依赖logback.xml测试 具体配置&#xff0c;可参看&#xff1a;LogBack日志学习&#xff08;全面&#xff09; logback使用实战 log4j使用实战 log4j2使用实战 Java日志系统之Logback Java日志系统之Slf4j Java日志系统之Log4j 引入logback的依赖 …

JAVA观察者模式-案例

Java观察者模式 import java.util.ArrayList; import java.util.List; // 主题接口&#xff0c;定义了添加、删除观察者等方法 interface Subject { void register(Observer observer); void unregister(Observer observer); void notifyObservers(String message); …

FPGA的斐波那契数列Fibonacci设计verilog,代码和视频

名称&#xff1a;斐波那契数列Fibonacci设计verilog 软件&#xff1a;Quartus 语言&#xff1a;Verilog 代码功能&#xff1a; 设计一个产生斐波那契数列&#xff08;也叫黄金分割数列&#xff09;的硬件电路: 斐波那契数列中每个数为其相邻前两个数的和:即FNFN1FN2,(数列…

探索数据结构世界之排序篇章(超级详细,你想看的都有)

-文章开头必看 1.&#xff01;&#xff01;&#xff01;本文排序默认都是排升序 2.排序是否稳定值指指排完序之后相同数的相对位置是否改变 3.代码相关解释我都写在注释中了&#xff0c;方便对照着看 1.插入排序1.1直接插入排序1.2希尔排序1.2.1单趟1.2.2多趟基础版——排完一…

Python:函数篇(每周练习)

编程题&#xff1a; Python第四章作业&#xff08;初级&#xff09; (educoder.net) 题一&#xff1a;无参无返回值函数 def print_hi_human(): # 函数名用小写字母print("人类&#xff0c;你好&#xff01;")if __name__ __main__:print_hi_human() 题二&#…

DailyPractice.2023.10.22

文章目录 1.[39. 组合总和]2.[22. 括号生成]3.[79. 单词搜索]4.[131. 分割回文串] 1.[39. 组合总和] 39. 组合总和 class Solution { public: vector<vector<int>> res; vector<int> path;void dfs(vector<int>& candidates,int target,int star…

如何支持h.265视频

前言 略 h.265视频 h.265是一种视频编码格式。 随着视频编码技术的发展&#xff0c;相比H.264, H.265同等画质体积仅为一半、带宽占用省一半、画质更细腻等诸多优势。 但Web浏览器还不支持H.265的解码播放&#xff0c;因此基于Web Assembly(封装FFmpeg)、JS解封装、Canvas投…

【读书笔记】《软技能》

句子摘抄&#xff1a; 软技能-代码之外的生存指南 “自强不息 孜孜不倦” 强调了坚持不懈、不断奋斗和追求进步的精神。无论遇到多少困难和挫折&#xff0c;都要坚持努力&#xff0c;不断提高自己&#xff0c;不知疲倦地追求目标。这句谚语鼓励人们积极进取&#xff0c;不轻言…

设计模式篇---组合模式

文章目录 概念结构实例总结 概念 组合模式&#xff1a;组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象。 当我们开发中遇到树形结构的业务时&#xff0c;可以考虑使用组合模式。&#xff08;我也没有想明白为啥…

【3D 图像分割】基于 Pytorch 的 VNet 3D 图像分割1(综述篇)

在上一个关于3D 目标的任务&#xff0c;是基于普通CNN网络的3D分类任务。在这个任务中&#xff0c;分类数据采用的是CT结节的LIDC-IDRI数据集&#xff0c;其中对结节的良恶性、毛刺、分叶征等等特征进行了各自的等级分类。感兴趣的可以直接点击下方的链接&#xff0c;直达学习&…

在 Android 10 中访问/proc/net/route权限被拒绝

在 Android 10 中访问/proc/net/route权限被拒绝 问题分析完整代码问题 FileReader fr = new FileReader(“/proc/net/route”);在 Android 10 中访问/proc/net/route权限被拒绝 分析 运行/proc/net/route命令并处理其输出: val runtime = Runtime.getRuntime() val proc …

Mysql数据库 2.SQL语言 数据类型与字段约束

Mysql数据类型 数据类型&#xff1a;指的是数据表中的列文件支持存放的数据类型 1.数值类型 Mysql当中有多种数据类型可以存放数值&#xff0c;不同的类型存放的数值的范围或者形式是不同的 注&#xff1a;前三种数字类型我们在实际研发中用的很少&#xff0c;一般整数类型…

[论文笔记]NEZHA

引言 今天带来华为诺亚方舟实验室提出的论文NEZHA,题目是 针对中文中文语言理解神经网络上下文表示(NEural contextualiZed representation for CHinese lAnguage understanding),为了拼出哪吒。 预训练语言模型由于具有通过对大型语料库进行预训练来捕获文本中深层上下文信…

【每日一题Day352】LC1726同积元组 | 哈希表+排列组合

同积元组【LC1726】 给你一个由 不同 正整数组成的数组 nums &#xff0c;请你返回满足 a * b c * d 的元组 (a, b, c, d) 的数量。其中 a、b、c 和 d 都是 nums 中的元素&#xff0c;且 a ! b ! c ! d 。 思路 求出所有二元组的积及其出现次数&#xff0c;假设某个积出现的次…

空中计算(Over-the-Air Computation)学习笔记

文章目录 写在前面 写在前面 本文是论文A Survey on Over-the-Air Computation的阅读笔记&#xff1a; 通信和计算通常被视为独立的任务。 从工程的角度来看&#xff0c;这种方法是非常有效的&#xff0c;因为可以执行孤立的优化。 然而&#xff0c;对于许多面向计算的应用程序…

Docker镜像制作

目录 Dockfile是什么 构建镜像的三个步骤 dockerfile内容基础知识 docker执行一个Dockerfile脚本的大致流程 Dockerfile指令 FROM MAINTAINER RUN EXPOSE WORKDIR ENV ADD COPY VOLUME USER ONBUILD CMD ENTRYPOINT CMD和ENTRYPOINT区别 构建dockerfile Do…

shell之常见网络命令介绍

shell之常见网络命令介绍 1&#xff09;ifconfig 用于配置网络接口。可以用于开启、关闭和设置网络接口的参数&#xff0c;如IP地址、子网掩码、MAC地址等。 ifconfig eth0 192.168.1.1 netmask 255.255.255.0 up上述命令将设置eth0网络接口的IP地址为192.168.1.1&#xff0c;子…

leetcode(2)栈

leetcode 155 最小栈 stack相当于栈&#xff0c;先进后出 存储全部栈元素 [-3,2,-1] min_stack,存储栈当前位置最小的元素 [-3,-3,-3] class MinStack:def __init__(self):self.stack []self.min_stack [math.inf]def push(self, x: int) :self.stack.append(x)self.min_sta…