常用的c++特性-->day02

news2024/11/25 6:57:41

c++11新特性

  • 可调用对象
    • 案例
    • 分析
  • 可调用对象包装器
    • 语法
    • 案例
    • 可调用对象包装器作为函数参数
    • 补充:类型转换运算符
      • 案例
  • 可调用对象绑定器
    • 语法格式
    • 绑定非类成员函数/变量
      • 案例1
      • 案例2
      • 案例3
      • 案例4
    • 绑定类成员函数/变量
  • lambda表达式
    • 捕获列表
    • 案例1
    • 返回值
    • 案例2 --> 包装器绑定器
  • 左值和右值
    • 右值引用
      • 书写规则
    • 案例
    • 右值引用的作用
      • 案例分析
      • 案例分析2
    • &&注意事项
  • 转移和完美转发
    • move资源的转移
    • forward
      • 函数原型
      • 案例
  • 共享智能指针
    • shared_ptr的初始化
      • 通过构造函数初始化
      • 通过拷贝函数进行初始化
      • 移动构造函数初始化
      • 使用make_shared
      • 通过reset方法初始化
      • 获取原始指针
    • shared_ptr的使用
    • 指定删除器
      • 指定删除器简单案例
      • 指定删除器复杂案例
    • c++自带的删除器
      • 自动删除简单案例
      • 自动删除复杂案例
  • 独占智能指针
    • unique_ptr的初始化
    • unique_ptr删除器
    • 补充
  • 弱引用智能指针
    • weak_ptr初始化
    • use_count()
    • expired()
    • lock()
    • reset()
  • 共享智能指针的注意事项
    • 不能使用一个原始地址初始化多个共享智能指针
    • 函数不能返回管理this的共享智能指针对象
      • 解决方案
    • 共享智能指针不能循环引用

可调用对象

案例

#include <iostream>
#include <string>
#include <vector>
using namespace std;

using funcptr = void(*)(int, string);

int print(int a, double b)
{
    cout << a << ", " << b << endl;
    return 0;
}
// 定义函数指针
int (*func)(int, double) = &print;

struct Test
{
    int m_id;
    // ()操作符重载
    void operator()(string msg)
    {
        cout << "msg: " << msg << endl;
    }

    //属于类的
    static void world(int a, string b)
    {
        cout << "name: " << b << ", age: " << a << endl;
    }

    //属于对象
    void hello(int a, string b)
    {
        cout << "name: " << b << ", age: " << a << endl;
    }

    // 将类对象转换为函数指针 ----> 指定world被调用
    operator funcptr()
    {
        //return hello;//err
        return world;
    }
};

int main(void)
{
	//函数指针
    func(1, 2);

	//仿函数
    Test t;
    t("hello world");

    Test tt;
    // 对象转换为函数指针, 并调用
    tt(19, "Monkey D. Luffy");
    //-----------------------------------------------------------------------------
    //类的函数指针
    //左侧是不属于类的函数指针,右侧是属于类的函数指针,hello是属于对象的
    //funcptr f = Test::hello;//err
    
    //函数名就是地址 下面两个写法都对
    funcptr f1 = Test::world;
    f1(10, "冬狮郎");
    funcptr f2 = &Test::world;
    f2(0, "黑崎一护");

    using fptr = void(Test::*)(int, string);
    fptr f3 = &Test::hello;
    Test ttt;
    (ttt.*f3)(2, "碎蜂");
    //---------------------------------------------------------------
    //类的成员指针(变量)
    using ptr1 = int Test::*;
    ptr1 pt = &Test::m_id;
    ttt.*pt = 1008611;
    cout << "m_id:" << ttt.m_id << endl;

    return 0;
}

在这里插入图片描述

分析

#include <iostream>
#include <string>
#include <vector>
using namespace std;

using func_ptr = void(*)(int, string);
struct Test
{
    static void print(int a, string b)
    {
        cout << "name: " << b << ", age: " << a << endl;
    }

    // 将类对象转换为函数指针
    operator func_ptr()
    {
        return print;
    }
};

int main(void)
{
    Test t;
    // 对象转换为函数指针, 并调用
    t(19, "Monkey D. Luffy");

    return 0;
}

类型转换语法:

operator target_type()
{
    return expression;
}

在这段代码里,target_type 是目标类型,表示希望对象转换成的类型;expression 是返回的值,通常是目标类型的实例或者符合目标类型的值

可调用对象包装器

std::function是可调用对象的包装器。它是一个类模板,可以容纳除了类(非静态)成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们。

语法

#include <functional>
std::function<返回值类型(参数类型列表)> diy_name = 可调用对象;

=后面接可调用对象
function包装函数—>成为可调用对象

案例

#include <iostream>
#include <string>
#include <vector>
#include <functional>
using namespace std;

using funcptr = void(*)(int, string);

int print(int a, double b)
{
    cout << a << ", " << b << endl;
    return 0;
}
// 定义函数指针
int (*func)(int, double) = &print;

struct Test
{
    int m_id;
    // ()操作符重载 >>> 仿函数
    void operator()(string msg)
    {
        cout << "msg: " << msg << endl;
    }

    //属于类的
    static void world(int a, string b)
    {
        cout << "name: " << b << ", age: " << a << endl;
    }

    //属于对象
    void hello(int a, string b)
    {
        cout << "name: " << b << ", age: " << a << endl;
    }

    // 将类对象转换为函数指针 ----> 指定world被调用
    operator funcptr()
    {
        //return hello;//err
        return world;
    }
};

int main(void)
{
    //1、函数指针
    func(1, 2.2);

    //2、仿函数
    Test t;
    t("hello world");

    Test tt;
    //对象转换为函数指针, 并调用 >>>>>>>>>>>>>>>>>>>> operator 对应函数
    tt(19, "Monkey D. Luffy");

    //3、类的函数指针
    //左侧是不属于类的函数指针,右侧是属于类的函数指针
    //funcptr f = Test::hello;//err
    
    //函数名就是地址 下面两个写法都对
    funcptr f1 = Test::world;
    f1(10, "冬狮郎");
    funcptr f2 = &Test::world;
    f2(0, "黑崎一护");

    using fptr = void(Test::*)(int, string);
    fptr f3 = &Test::hello;
    Test ttt;
    (ttt.*f3)(2, "碎蜂");

    //4、类的成员指针(变量)
    using ptr1 = int Test::*;
    ptr1 pt = &Test::m_id;
    ttt.*pt = 1008611;
    cout << "m_id:" << ttt.m_id << endl;

//*********************************************************************************************
    cout << endl << endl << endl;
//*********************************************************************************************

    //1、包装普通函数
    function<void(int, double)>f4 = print;
    f4(1, 2.2);
    //2、包装静态函数
    function<void(int, string)>f5 = Test::world;
    f5(5, "蓝染");
    //3、包装仿函数
    Test ta;
    function<void(string)>f6 = ta;
    f6("浦原喜助");
    //4、包装转换为函数指针的对象
    Test tb;
    function<void(int, string)>f7 = tb;
    f7(11, "更木剑八");

    return 0;
}

在这里插入图片描述

可调用对象包装器作为函数参数

#include <iostream>
#include <string>
#include <vector>
#include <functional>
using namespace std;

using funcptr = void(*)(int, string);

int print(int a, double b)
{
    cout << a << ", " << b << endl;
    return 0;
}
// 定义函数指针
int (*func)(int, double) = &print;

struct Test
{
    int m_id;
    // ()操作符重载 >>> 仿函数
    void operator()(string msg)
    {
        cout << "msg: " << msg << endl;
    }

    //属于类的
    static void world(int a, string b)
    {
        cout << "name: " << b << ", age: " << a << endl;
    }

    //属于对象
    void hello(int a, string b)
    {
        cout << "name: " << b << ", age: " << a << endl;
    }

    // 将类对象转换为函数指针 ----> 指定world被调用
    operator funcptr()
    {
        //return hello;//err
        return world;
    }
};

class A
{
public:
    // 构造函数参数是一个包装器对象
    A(const function<void(int,string)>& f) : callback(f)
    {
    }

    void notify(int id, string name)
    {
        callback(id, name); // 调用通过构造函数得到的函数指针
    }
private:
    function<void(int,string)> callback;
};

int main(void)
{
    //1、函数指针
    func(1, 2.2);

    //2、仿函数
    Test t;
    t("hello world");

    //2.5、是一个可被转换为函数指针的类对象
    Test tt;
    //对象转换为函数指针, 并调用 >>>>>>>>>>>>>>>>>>>> operator 对应函数
    tt(19, "Monkey D. Luffy");

    //3、类的函数指针
    //左侧是不属于类的函数指针,右侧是属于类的函数指针
    //funcptr f = Test::hello;//err
    
    //函数名就是地址 下面两个写法都对
    funcptr f1 = Test::world;
    f1(10, "冬狮郎");
    funcptr f2 = &Test::world;
    f2(0, "黑崎一护");

    using fptr = void(Test::*)(int, string);
    fptr f3 = &Test::hello;
    Test ttt;
    (ttt.*f3)(2, "碎蜂");

    //4、类的成员指针(变量)
    using ptr1 = int Test::*;
    ptr1 pt = &Test::m_id;
    ttt.*pt = 1008611;
    cout << "m_id:" << ttt.m_id << endl;

//*********************************************************************************************
    cout << endl << endl;
//*********************************************************************************************

    //1、包装普通函数
    function<void(int, double)>f4 = print;
    f4(1, 2.2);
    //2、包装静态函数
    function<void(int, string)>f5 = Test::world;
    f5(5, "蓝染");
    //3、包装仿函数
    Test ta;
    function<void(string)>f6 = ta;
    f6("浦原喜助");
    //4、包装转换为函数指针的对象
    Test tb;
    function<void(int, string)>f7 = tb;
    f7(11, "更木剑八");

//*********************************************************************************************
    cout << endl << endl;
//*********************************************************************************************
    
    //可调用函数包装器作为函数参数
    A aa(Test::world);
    aa.notify(12, "涅茧利");

    Test tc;
    A bb(tc);
    bb.notify(7, "狛村左阵");
    return 0;
}

在这里插入图片描述
Test 类定义了一个 operator funcptr() 转换函数,它的作用是将 Test 类的对象转换为 funcptr 类型的函数指针(即 void(*)(int, string))。当将 tb 传递给 f7 时,实际上调用了这个转换操作符。

补充:类型转换运算符

operator 目标类型()
{
    // 转换逻辑
}

案例

#include <iostream>
using namespace std;

class Box {
public:
    Box(double v) : volume(v) {}

    // 类型转换运算符:将 Box 对象转换为 double 类型
    operator double() const {
        return volume;
    }

private:
    double volume;
};

int main() {
    Box b(100.0);
    double volume = b; // 隐式调用 operator double()
    cout << "Volume: " << volume << endl;
    return 0;
}

在这里插入图片描述

可调用对象绑定器

可调用对象绑定器:
1、将可调用对象与其参数一起绑定成一个仿函数
2、将多元(参数个数为n,n>1)可调用对象转换为一元或者(n-1)元可调用对象,即只绑定部分参数。

语法格式

// 绑定非类成员函数/变量
auto f = std::bind(可调用对象地址, 绑定的参数/占位符); //>>>>>>>>>>>>>>>>静态函数可以使用这种方式
// 绑定类成员函/变量
auto f = std::bind(类函数/成员地址, 类实例对象地址, 绑定的参数/占位符);// >>>>>>>>>>>>>>>>>>>>绑定的是成员第三个参数可以省略

绑定非类成员函数/变量

案例1

#include <iostream>
#include <functional>
using namespace std;
//通过bind得到第二个参数
void callFunc(int x, const function<void(int)>& f)
{
    if (x % 2 == 0)
    {
        f(x);
    }
}
void output(int x)
{
    cout << x << " ";
}

void output_add(int x)
{
    cout << x + 10 << " ";
}

int main(void)
{
    // 使用绑定器绑定可调用对象和参数
    auto f1 = bind(output, placeholders::_1);// >>>>>> f1类型std::function<void(int)>
    f1(100);
    cout << endl;
    for (int i = 0; i < 10; ++i)
    {
        callFunc(i, f1);
    }
    cout << endl;

    auto f2 = bind(output_add, placeholders::_1);// >>>>>>> f2类型std::function<void(int)>
    for (int i = 0; i < 10; ++i)
    {
        callFunc(i, f2);
    }
    cout << endl;

    return 0;
}

在上面的程序中,使用了std::bind绑定器,在函数外部通过绑定不同的函数,控制了最后执行的结果。std::bind绑定器返回的是一个仿函数类型,得到的返回值可以直接赋值给一个std::function,在使用的时候我们并不需要关心绑定器的返回值类型,使用auto进行自动类型推导就可以了。
placeholders::_1是一个占位符,代表这个位置将在函数调用时被传入的第一个参数所替代。同样还有其他的占位符placeholders::_2、placeholders::_3、placeholders::_4、placeholders::_5等……
在这里插入图片描述

案例2

#include <iostream>
#include <functional>
using namespace std;

void output(int x, int y)
{
    cout << x << " " << y << endl;
}

int main(void)
{
    // 使用绑定器绑定可调用对象和参数, 并调用得到的仿函数
    bind(output, 1, 2)();
    bind(output, placeholders::_1, 2)(10);
    bind(output, 2, placeholders::_1)(10);

    // error, 调用时没有第二个参数
    // bind(output, 2, placeholders::_2)(10);
    // 调用时第一个参数10被吞掉了,没有被使用
    bind(output, 2, placeholders::_2)(10, 20);

    bind(output, placeholders::_1, placeholders::_2)(10, 20);
    bind(output, placeholders::_2, placeholders::_1)(10, 20);


    return 0;
}

在这里插入图片描述

案例3

#include <iostream>
#include <functional>
using namespace std;

void callFunc(int x, int y, const function<void(int, int)>& f)
{
    if (x % 2 == 0)
    {
        f(x, y);
    }
}

void output_add(int x, int y)
{
    cout << "x: " << x << ",y: " << y <<
        ",x+y: " << x + y << endl;
}

int main(void)
{
    for (int i = 0; i < 10; ++i)
    {
        //bind绑定固定的实参函数调用的时候是不会生效的
        //如果是占位符是可以生效的
        //绑定的是output_add函数 i+100 i+200是为这个函数提供的参数
        auto f1 = bind(output_add, i + 100, i + 200);// >>>>>> f1被推导为function<void<int,int>>类型
        callFunc(i, i, f1);
        cout << endl;
        auto f2 = bind(output_add, placeholders::_1, placeholders::_2);
        callFunc(i, i, f2);
    }
    return 0;
}

在这里插入图片描述

案例4

#include <iostream>
#include <functional>
using namespace std;

void callFunc(int x, int y, const function<void(int, int)>& f)
{
    if (x % 2 == 0)
    {
        f(x, y);
    }
}

void output_add(int x, int y)
{
    cout << "x: " << x << ",y: " << y <<
        ",x+y: " << x + y << endl;
}

int main(void)
{
    for (int i = 0; i < 10; ++i)
    {
        callFunc(i, i, bind(output_add, placeholders::_1, placeholders::_2));
    }
    return 0;
}

在这里插入图片描述

绑定类成员函数/变量

#include <iostream>
#include <functional>
using namespace std;

class Test
{
public:
    void output(int x, int y)
    {
        cout << "x: " << x << ", y: " << y << endl;
    }
    int m_number = 100;
};

int main(void)
{
    Test t;
    // 绑定类成员函数
    function<void(int, int)> f1 = bind(&Test::output, &t, placeholders::_1, placeholders::_2);
    auto f2 = bind(&Test::output, &t, 14, placeholders::_1);
    // 绑定类成员变量(公共)
    //f3可读可写 >>>>>>>>> &
    function<int& (void)> f3 = bind(&Test::m_number, &t);
    auto f4 = bind(&Test::m_number, &t);// >>>>>>>>f3和f4类型不一样

    // 调用
    f1(520, 1314);
    f2(38);
    cout << "f3():" << f3() << endl;
    f3() = 2333;
    cout << "t.m_number: " << t.m_number << endl;
    f4() = 3332;
    cout << "t.m_number: " << t.m_number << endl;

    return 0;
}

在这里插入图片描述

使用绑定器绑定的类成员变量m_number得到的仿函数被存储到了类型为function<int&(void)>的包装器对象f3中,并且可以在需要的时候修改这个成员。其中int是绑定的类成员的类型,并且允许修改绑定的变量,因此需要指定为变量的引用,由于没有参数因此参数列表指定为void。
使用绑定器绑定的类成员变量m_number得到的仿函数被存储到了类型为function<int&(void)>的包装器对象f3中,并且可以在需要的时候修改这个成员。其中int是绑定的类成员的类型,并且允许修改绑定的变量,因此需要指定为变量的引用,由于没有参数因此参数列表指定为void。

lambda表达式

lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda表达式的语法形式简单归纳如下:

[capture](params) opt -> ret {body;};

其中capture是捕获列表,params是参数列表,opt是函数选项,ret是返回值类型,body是函数体。
捕获列表[]: 捕获一定范围内的变量
参数列表(): 和普通函数的参数列表一样,如果没有参数参数列表可以省略不写。

auto f = [](){return 1;}	// 没有参数, 参数列表为空
auto f = []{return 1;}		// 没有参数, 参数列表省略不写

opt 选项, 不需要可以省略
mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用throw();
返回值类型:在C++11中,lambda表达式的返回值是通过返回值后置语法来定义的。
函数体:函数的实现,这部分不能省略,但函数体可以为空。

捕获列表

[] - 不捕捉任何变量
[&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
[=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获)
拷贝的副本在匿名函数体内部是只读的
[=, &foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量 foo
[bar] - 按值捕获 bar 变量, 同时不捕获其他变量
[&bar] - 按引用捕获 bar 变量, 同时不捕获其他变量
[this] - 捕获当前类中的this指针
让lambda表达式拥有和当前类成员函数同样的访问权限
如果已经使用了 & 或者 =, 默认添加此选项

案例1

#include <iostream>
#include <functional>
using namespace std;

void func(int x, int y)
{
	int a = 9;
	int b = 6;
	//由于是=上面的b和下面的b是存在于不同内存的
	[=, &x]()mutable{
			int c = a;
			int d = x;
			d = d + 100;
			b = b + 10;// >>>>>>> mutable是为了这里的b服务的
			cout << "c:" << c << endl;
			cout << "d:" << d << endl;
			cout << "b:" << b << endl;
	}();
	cout << "b:" << b << endl;
}

int main()
{
	func(1000,1000);
	return 0;
}

在这里插入图片描述

返回值

一般情况下,不指定lambda表达式的返回值,编译器会根据return语句自动推导返回值的类型,但需要注意的是labmda表达式不能通过列表初始化自动推导出返回值类型。

// ok,可以自动推导出返回值类型
auto f = [](int i)
{
    return i;
}

// error,不能推导出返回值类型
auto f1 = []()
{
    return {1, 2};	// 基于列表初始化推导返回值,错误
}

案例2 --> 包装器绑定器

#include <iostream>
#include <functional>
using namespace std;

void func(int x, int y)
{
	int a;
	int b;
	using ptr = void(*)(int);
	//捕获列表没有任何参数 >>> 可以被看作为函数指针
	ptr p1 = [](int x)
		{
			cout << "x: " << x << endl;
		};
	p1(11);// >>>>>>>>>>>>>>>>>>> 11作为参数传入到lambda表达式中

	//err
		//ptr p2 = [=](int x)
		//{
		//	cout << "x: " << x << endl;
		//};
		//p2(11);

	function<void(int)> fff = [=](int x)
		{
			cout << "x: " << x << endl;
		};
	fff(11);

	//由于绑定器绑定是仿函数
	//auto推导是仿函数
	//包装器推导是包装器对象
	function<void(int)> ffff = bind([=](int x)
		{
			cout << "x: " << x << endl;
		}, placeholders::_1);
	ffff(11);

}

int main()
{
	func(10, 20);

	return 0;
}

左值和右值

左值是指存储在内存中、有明确存储地址(可取地址)的数据
右值是指可以提供数据值的数据(不可取地址)

右值引用

书写规则

#include <iostream>
#include <functional>
using namespace std;

int main()
{
	//1、左值
	int num = 9;
	//2、左值引用
	int& a = num;
	//右值
	//3、右值引用
	int&& b = 8;
	//4、常量右值引用
	const int&& d = 6;
	//const int&& e = b;//err
	//int&& f = b;//err
	//5、常量左值引用
	const int& c = num;
	//常量的左值引用可以使用常量的右值引用进行初始化
	const int& f = b;
	const int& g = d;
}

案例

#include <iostream>
using namespace std;

int&& value = 520;
class Test
{
public:
    Test()
    {
        cout << "construct: my name is jerry" << endl;
    }
    Test(const Test& a)
    {
        cout << "copy construct: my name is tom" << endl;
    }
};

Test getObj()
{
    return Test();
}

int main()
{
    int a1;
    int &&a2 = a1;        // error
    Test& t = getObj();   // error
    Test && t = getObj();
    const Test& tt = getObj();
    return 0;
}

在这里插入图片描述
在上面的例子中int&& value = 520;里面520是纯右值,value是对字面量520这个右值的引用。
在int &&a2 = a1;中a1虽然写在了=右边,但是它仍然是一个左值,使用左值初始化一个右值引用类型是不合法的。
在Test& t = getObj()这句代码中语法是错误的,右值不能给普通的左值引用赋值。
在Test && t = getObj();中getObj()返回的临时对象被称之为将亡值,t是这个将亡值的右值引用。
const Test& t = getObj()这句代码的语法是正确的,常量左值引用是一个万能引用类型,它可以接受左值、右值、常量左值和常量右值。

右值引用的作用

右值引用延迟内存的生命周期
在C++中在进行对象赋值操作的时候,很多情况下会发生对象之间的深拷贝,如果堆内存很大,这个拷贝的代价也就非常大,在某些情况下,如果想要避免对象的深拷贝,就可以使用右值引用进行性能的优化。

案例分析

vs2019

#include <iostream>
using namespace std;

class Test
{
public:
    Test() : m_num(new int(100))
    {
        cout << "construct: my name is jerry" << endl; 
        cout << "m_num: " << m_num << endl;
    }

    Test(const Test& a) : m_num(new int(*a.m_num))
    {
        cout << "copy construct: my name is tom" << endl;
    }

    ~Test()
    {
        cout << "destruct Test class ..." << endl;
        delete m_num;
    }

    int* m_num;
};

Test getObj()
{
    Test t;
    return t;
}

int main()
{
    Test t = getObj();
}

在这里插入图片描述
上述是消耗资源的因为getObj调用的时候只是创建了一个t对象然后返回,拷贝构造析构都是需要消耗资源的,因此需要让t对象直接使用getObj,因此用右值引用
优化上面代码(vs2019下):

#include <iostream>
using namespace std;

class Test
{
public:
    Test() : m_num(new int(100))
    {
        cout << "construct: my name is jerry" << endl; 
        cout << "m_num: " << m_num << endl;
    }

    Test(const Test& a) : m_num(new int(*a.m_num))
    {
        cout << "copy construct: my name is tom" << endl;
    }

    //浅拷贝
    //移动构造函数 -> 复用其它对象中的资源(堆内存)
    Test(Test&& a) :m_num(a.m_num)
    {
        a.m_num = nullptr;
        cout << "move construct..." << endl;
    }

    ~Test()
    {
        cout << "destruct Test class ..." << endl;
        delete m_num;
    }

    int* m_num;
};

Test getObj()
{
    Test t;
    return t;
}

int main()
{
    Test t = getObj();
    cout << "t.m_num: " << *t.m_num << endl;
    return 0;
};

右值引用具有移动语义,移动语义可以将资源(堆、系统对象等)通过浅拷贝从一个对象转移到另一个对象这样就能减少不必要的临时对象的创建、拷贝以及销毁,可以大幅提高C++应用程序的性能。

执行结果:

construct: my name is jerry
move construct: my name is sunny
destruct Test class ...
t.m_num: 100
destruct Test class ...

通过修改,在上面的代码给Test类添加了移动构造函数(参数为右值引用类型),这样在进行Test t = getObj();操作的时候并没有调用拷贝构造函数进行深拷贝,而是调用了移动构造函数,在这个函数中只是进行了浅拷贝,没有对临时对象进行深拷贝,提高了性能。

在测试程序中getObj()的返回值就是一个将亡值,也就是说是一个右值,在进行赋值操作的时候如果=右边是一个右值,那么移动构造函数就会被调用。移动构造中使用了右值引用,会将临时对象中的堆内存地址的所有权转移给对象t,这块内存被成功续命,因此在t对象中还可以继续使用这块内存。

案例分析2

右值引用分为:普通右值和将亡右值

#include <iostream>
using namespace std;

class Test
{
public:
    Test() : m_num(new int(100))
    {
        cout << "construct: my name is jerry" << endl; 
        cout << "m_num: " << m_num << endl;
    }

    Test(const Test& a) : m_num(new int(*a.m_num))
    {
        cout << "copy construct: my name is tom" << endl;
    }

    //浅拷贝
    //移动构造函数 -> 复用其它对象中的资源(堆内存)
    Test(Test&& a) :m_num(a.m_num)
    {
        a.m_num = nullptr;
        cout << "move construct: my name is sunny" << endl;
    }

    ~Test()
    {
        cout << "destruct Test class ..." << endl;
        delete m_num;
    }

    int* m_num;
};

Test getObj1()
{
    Test t;
    return t;
}

//返回的是临时对象是不能够被取地址的
Test getObj2()
{
    return Test();
}

//将临时对象转换为右值引用
Test&& getObj3()
{
    return Test();
}

int main()
{
    //两种方法 >>>>>> 要求右侧是临时对象 
    Test t = getObj1();// >>>>>>>>>>>> 返回临时对象调用移动构造函数
    Test&& t1 = getObj1();
    cout << "t.m_num: " << *t1.m_num << endl;

    //复用了即将释放对象的所有资源
    Test&& t2 = getObj2();// >>>>>>>>>>>>> 没有使用移动构造函数 要求右侧是一个临时不能取地址的对象
    cout << "t.m_num: " << *t2.m_num << endl;

    return 0;
};

解释移动构造函数:

    Test(Test&& a) :m_num(a.m_num)
    {
        a.m_num = nullptr;
        cout << "move construct: my name is sunny" << endl;
    }

源对象 a 不再拥有指向原始资源的指针。
在源对象析构时,它不会错误地释放已经转移给目标对象的资源。
目标对象在析构时将正确地释放它所接管的资源

&&注意事项

template<typename T>
void f(T&& param);
void f1(const T&& param);
f(10); 	//右值引用
int x = 10;
f(x); //左值引用
f1(x);	// error, x是左值
f1(10); // ok, 10是右值

第4行中,对于f(10)来说传入的实参10是右值,因此T&&表示右值引用
第6行中,对于f(x)来说传入的实参是x是左值,因此T&&表示左值引用
第7行中,f1(x)的参数是const T&&不是未定引用类型,不需要推导,本身就表示一个右值引用

int main()
{
    int x = 520, y = 1314;
    auto&& v1 = x;
    auto&& v2 = 250;
    decltype(x)&& v3 = y;   // error
    cout << "v1: " << v1 << ", v2: " << v2 << endl;
    return 0;
};

第4行中 auto&&表示一个整形的左值引用
第5行中 auto&&表示一个整形的右值引用
第6行中decltype(x)&&等价于int&&是一个右值引用不是未定引用类型,y是一个左值,不能使用左值初始化一个右值引用类型。

由于上述代码中存在T&&或者auto&&这种未定引用类型,当它作为参数时,有可能被一个右值引用初始化,也有可能被一个左值引用初始化,在进行类型推导时右值引用类型(&&)会发生变化,这种变化被称为引用折叠。在C++11中引用折叠的规则如下:
通过右值推导 T&& 或者 auto&& 得到的是一个右值引用类型
通过非右值(右值引用、左值、左值引用、常量右值引用、常量左值引用)推导 T&& 或者 auto&& 得到的是一个左值引用类型

int&& a1 = 5;
auto&& bb = a1;
auto&& bb1 = 5;

int a2 = 5;
int &a3 = a2;
auto&& cc = a3;
auto&& cc1 = a2;

const int& s1 = 100;
const int&& s2 = 100;
auto&& dd = s1;
auto&& ee = s2;

const auto&& x = 5;

第2行:a1为右值引用,推导出的bb为左值引用类型
第3行:5为右值,推导出的bb1为右值引用类型
第7行:a3为左值引用,推导出的cc为左值引用类型
第8行:a2为左值,推导出的cc1为左值引用类型
第12行:s1为常量左值引用,推导出的dd为常量左值引用类型
第13行:s2为常量右值引用,推导出的ee为常量左值引用类型
第15行:x为右值引用,不需要推导,只能通过右值初始化

#include <iostream>
using namespace std;

void printValue(int &i)
{
    cout << "l-value: " << i << endl;
}

void printValue(int &&i)
{
    cout << "r-value: " << i << endl;
}

void forward(int &&k)
{
    printValue(k);
}

int main()
{
    int i = 520;
    printValue(i);
    printValue(1314);
    forward(250);

    return 0;
};

在这里插入图片描述

转移和完美转发

move资源的转移

move:将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝
在C++11添加了右值引用,并且不能使用左值初始化右值引用,如果想要使用左值初始化一个右值引用需要借助std::move()函数,使用std::move方法可以将左值转换为右值。使用这个函数并不能移动任何东西,而是和移动构造函数一样都具有移动语义,将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。

#include <iostream>
#include <list>
using namespace std;

class Test
{
public:
    Test() : m_num(new int(100))
    {
        cout << "construct: my name is jerry" << endl; 
        cout << "m_num: " << m_num << endl;
    }

    Test(const Test& a) : m_num(new int(*a.m_num))
    {
        cout << "copy construct: my name is tom" << endl;
    }

    //浅拷贝
    //移动构造函数 -> 复用其它对象中的资源(堆内存)
    Test(Test&& a) :m_num(a.m_num)
    {
        a.m_num = nullptr;
        cout << "move construct: my name is sunny" << endl;
    }

    ~Test()
    {
        cout << "destruct Test class ..." << endl;
        delete m_num;
    }

    int* m_num;
};

Test getObj1()
{
    Test t;
    return t;
}


Test getObj2()
{
    return Test();
}

//将临时对象转换为右值引用
Test&& getObj3()
{
    return Test();
}

int main()
{
	//如果getObj1返回的不是临时对象就不会调用移动构造函数,而是调用拷贝构造函数
    Test t = getObj1();// >>>>>>>>>>>> getObj1临时对象调用移动构造函数
    Test&& t1 = getObj1();
    cout << "t.m_num: " << *t1.m_num << endl;

    //复用了即将释放对象的所有资源
    Test&& t2 = getObj2();// >>>>>>>>>>>>> 没有使用移动构造函数 要求右侧是一个临时不能取地址的对象
    cout << "t.m_num: " << *t2.m_num << endl;

    //Test&& t3 = t2;//err
    Test&& t3 = move(t2);
    Test&& t4 = move(t);
    
    list<string> ls1{
        "hello","world","nihao","shijie",
    };
  
    list<string> ls2 = ls1;// >>>>>>>> 需要拷贝 效率低
    list<string> ls3 = move(ls1);
    return 0;
};

没有被编译器优化的结果

//Test t = getObj1();
construct: my name is jerry
m_num: <t.m_num地址>
move construct: my name is sunny
destruct Test class ...
//Test&& t1 = getObj1();
construct: my name is jerry
m_num: <新临时对象的m_num地址>
t.m_num: 100
// Test&& t2 = getObj2();
construct: my name is jerry
m_num: <新临时对象的m_num地址>
t.m_num: 100
//两个move
move construct: my name is sunny
move construct: my name is sunny
destruct Test class ...
destruct Test class ...
destruct Test class ...
destruct Test class ...
destruct Test class ...
destruct Test class ...

Test t = getObj1(); 可能触发移动构造函数(如果编译器没有优化掉)。由于返回的对象是临时对象,编译器将使用移动构造函数将资源从临时对象转移到 t。

forward

函数原型

template <class T> T&& forward (typename remove_reference<T>::type& t) noexcept;
template <class T> T&& forward (typename remove_reference<T>::type&& t) noexcept;

精简之后的样子

std::forward<T>(t);

当T为左值引用类型时,t将被转换为T类型的左值
当T不是左值引用类型时,t将被转换为T类型的右值

案例

#include <iostream>
using namespace std;

template<typename T>
void printValue(T& t)
{
    cout << "l-value: " << t << endl;
}

template<typename T>
void printValue(T&& t)
{
    cout << "r-value: " << t << endl;
}

template<typename T>
void testForward(T&& v)
{
    printValue(v);
    printValue(move(v));
    printValue(forward<T>(v));
    cout << endl;
}

int main()
{
    testForward(520);
    int num = 1314;
    testForward(num);
    //<int>类型右值 num被转换为int
    testForward(forward<int>(num));
    //<int&>类型左值
    testForward(forward<int&>(num));
    //<int&&>类型右值
    testForward(forward<int&&>(num));

    return 0;
}

在这里插入图片描述
forward(num) 让编译器将 num 作为右值传递给 testForward,这意味着虽然 num 是一个左值,使用 std::forward(num) 后,它会被转发为右值。
forward<int&>(num) 将 num 转发为 左值引用(int&)。这是因为你显式指定了 int&,即要求将 num 作为左值引用转发。
forward<int&&>(num) 会试图将 num 转发为右值引用。但是 num 是左值,左值不能绑定到右值引用(int&&)。因此,这样的代码会导致编译错误。

共享智能指针

C++11中提供了三种智能指针,使用这些智能指针时需要引用头文件:
std::shared_ptr:共享的智能指针
std::unique_ptr:独占的智能指针
std::weak_ptr:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视shared_ptr的。

shared_ptr的初始化

共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针shared_ptr 是一个模板类,如果要进行初始化有三种方式:通过构造函数、std::make_shared辅助函数以及reset方法。

通过构造函数初始化

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    // 使用智能指针管理一块 int 型的堆内存
    shared_ptr<int> ptr1(new int(520));
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    // 使用智能指针管理一块字符数组对应的堆内存
    shared_ptr<char> ptr2(new char[12]);
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    // 创建智能指针对象, 不管理任何内存
    shared_ptr<int> ptr3;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    // 创建智能指针对象, 初始化为空
    shared_ptr<int> ptr4(nullptr);
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

    return 0;
}

在这里插入图片描述

通过拷贝函数进行初始化

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    // 使用智能指针管理一块 int 型的堆内存
    shared_ptr<int> ptr1(new int(520));
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    shared_ptr<int> ptr5 = ptr1;
    cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    // 使用智能指针管理一块字符数组对应的堆内存
    shared_ptr<char> ptr2(new char[12]);
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    // 创建智能指针对象, 不管理任何内存
    shared_ptr<int> ptr3;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    // 创建智能指针对象, 初始化为空
    shared_ptr<int> ptr4(nullptr);
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

    return 0;
}

在这里插入图片描述

移动构造函数初始化

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
    shared_ptr<int> ptr1(new int(520));
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    //调用拷贝构造函数
    shared_ptr<int> ptr2(ptr1);
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    shared_ptr<int> ptr3 = ptr1;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    //调用移动构造函数
    shared_ptr<int> ptr4(std::move(ptr1));//移动给ptr4管理
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
    std::shared_ptr<int> ptr5 = std::move(ptr2);//给ptr5管理
    cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;

    return 0;
}

在这里插入图片描述
如果使用拷贝的方式初始化共享智能指针对象,这两个对象会同时管理同一块堆内存,堆内存对应的引用计数也会增加;如果使用移动的方式初始智能指针对象,只是转让了内存的所有权,管理内存的对象并不会增加,因此内存的引用计数不会变化。

使用make_shared

通过C++提供的std::make_shared() 就可以完成内存对象的创建并将其初始化给智能指针,函数原型如下:

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );

T:模板参数的数据类型
Args&&… args :要初始化的数据,如果是通过make_shared创建对象,需按照构造函数的参数列表指定

#include <iostream>
#include <string>
#include <memory>
using namespace std;

class Test
{
public:
    Test()
    {
        cout << "construct Test..." << endl;
    }
    Test(int x)
    {
        cout << "construct Test, x = " << x << endl;
    }
    Test(string str)
    {
        cout << "construct Test, str = " << str << endl;
    }
    ~Test()
    {
        cout << "destruct Test ..." << endl;
    }
};

int main()
{
    // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
    shared_ptr<int> ptr1 = make_shared<int>(520);
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    cout << endl;

    shared_ptr<Test> ptr2 = make_shared<Test>();
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;

    shared_ptr<Test> ptr3 = make_shared<Test>(520);
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;

    shared_ptr<Test> ptr4 = make_shared<Test>("我是要成为海贼王的男人!!!");
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
    return 0;
}

在这里插入图片描述

通过reset方法初始化

#include <iostream>
#include <string>
#include <memory>
using namespace std;

int main()
{
    // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
    shared_ptr<int> ptr1 = make_shared<int>(520);
    shared_ptr<int> ptr2 = ptr1;
    shared_ptr<int> ptr3 = ptr1;
    shared_ptr<int> ptr4 = ptr1;
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

    ptr4.reset();
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;

    return 0;
}

在这里插入图片描述
对于一个未初始化的共享智能指针,可以通过reset方法来初始化,当智能指针中有值的时候,调用reset会使引用计数减1。

获取原始指针

通过智能指针可以管理一个普通变量或者对象的地址,此时原始地址就不可见了。当我们想要修改变量或者对象中的值的时候,就需要从智能指针对象中先取出数据的原始内存的地址再操作,解决方案是调用共享智能指针类提供的get()方法,其函数原型如下:

T* get() const noexcept;
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <memory>

using namespace std;

int main()
{
    int len = 128;
    shared_ptr<char> ptr(new char[len]);
    // 得到指针的原始地址
    char* add = ptr.get();
    memset(add, 0, len);
    strcpy(add, "我是要成为海贼王的男人!!!");
    cout << "string: " << add << endl;

    shared_ptr<int> p(new int);
    *p = 100;
    cout << *p.get() << "  " << *p << endl;

    return 0;
}

在这里插入图片描述

shared_ptr的使用

方式1:

share_ptr<Test> ptr5=make_shared<Test>(8);
ptr5.reset(new Test("hello"));

Test* t=ptr5.get();
t->setValue(10000);
t->print();

方式2:

ptr5->setValue(121212);
ptr5->print();

指定删除器

这里是自己指定删除器不使用share_ptr自带的删除器
当智能指针管理的内存对应的引用计数变为0的时候,这块内存就会被智能指针析构掉了。另外,我们在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数,我们只需要进行实现,其调用是由智能指针完成的。
删除器可以在外部写也可以在内部写个匿名函数lambda表达式
删除器处理时return 0时候进行释放内存

指定删除器简单案例

shared_ptr<Test> ppp(new Test(100),[](Test* t)){
	//释放内存的操作
	delete t;
};
shared_ptr<Test> p1(new Test[5],[](Test* t)){
	//释放内存的操作
	delete []t;
};
#include <iostream>
#include <memory>
using namespace std;

// 自定义删除器函数,释放int型内存
void deleteIntPtr(int* p)
{
    delete p;
    cout << "int 型内存被释放了...";
}

int main()
{
    shared_ptr<int> ptr(new int(250), deleteIntPtr);
    return 0;
}

在这里插入图片描述

deleteIntPtr 函数: deleteIntPtr 函数会收到 shared_ptr 所管理的原始指针 p。然后,delete p; 会释放这块内存。并且在释放内存之后,deleteIntPtr 会打印消息 “int 型内存被释放了…”,指示内存已经被成功释放。

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    shared_ptr<int> ptr(new int(250), [](int *p)// >>>> p=new int(250)
        {
            delete p;
            cout << "释放!\n";
        });
    return 0;
}

在这里插入图片描述

指定删除器复杂案例

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <memory>
#include <cstring>  // for memset and strcpy

using namespace std;

template <typename T>
shared_ptr<T> make_share_array(size_t size)
{
    // 使用自定义 lambda 作为删除器
    return shared_ptr<T>(
        new T[size],  // 分配动态数组
        [](T* p) {
            cout << "自定义删除器:释放数组内存!\n";
            delete[] p;  // 使用 delete[] 正确释放数组
        }
    );
}

int main()
{
    int len = 128;
    // 使用 make_share_array 创建共享的 char 数组
    shared_ptr<char> ptr = make_share_array<char>(len);

    // 得到指针的原始地址
    char* add = ptr.get();
    memset(add, 0, len);  // 初始化数组内容为 0
    strcpy(add, "我是要成为海贼王的男人!!!");  // 拷贝字符串到数组
    cout << "string: " << add << endl;

    // 创建一个 shared_ptr 管理的 int 变量,使用自定义删除器
    shared_ptr<int> p(new int, [](int* p) {
        cout << "自定义删除器:释放 int 内存!\n";
        delete p;
    });
    *p = 100;
    cout << *p.get() << "  " << *p << endl;

    return 0;
}

c++自带的删除器

在C++11中使用shared_ptr管理动态数组时,需要指定删除器,因为std::shared_ptr的默认删除器不支持数组对象
在删除数组内存时,除了自己编写删除器,也可以使用C++提供的std::default_delete()函数作为删除器,这个函数内部的删除功能也是通过调用delete来实现的,要释放什么类型的内存就将模板类型T指定为什么类型即可。具体处理代码如下:

//删除数组类型内存 ---> Test[]
shared_ptr<Test> p2(new Test[5], default_delete<Test[]>());

自动删除简单案例

#include <iostream>
#include <memory>
using namespace std;

template <typename T>
shared_ptr<T> make_share_array(size_t size)
{
    // 返回匿名对象
    return shared_ptr<T>(new T[size], default_delete<T[]>());
}

int main()
{
    shared_ptr<int> ptr1 = make_share_array<int>(10);
    cout << ptr1.use_count() << endl;
    shared_ptr<char> ptr2 = make_share_array<char>(128);
    cout << ptr2.use_count() << endl;
    return 0;
}

自动删除复杂案例

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <memory>
#include <cstring>  // for memset and strcpy

using namespace std;

template <typename T>
shared_ptr<T> make_share_array(size_t size)
{
    // 返回匿名对象,使用 default_delete<T[]> 自动释放数组内存
    return shared_ptr<T>(new T[size], default_delete<T[]>());
}

int main()
{
    int len = 128;
    // 使用 make_share_array 创建共享的 char 数组
    shared_ptr<char> ptr = make_share_array<char>(len);

    // 得到指针的原始地址
    char* add = ptr.get();
    memset(add, 0, len);  // 初始化数组内容为 0
    strcpy(add, "我是要成为海贼王的男人!!!");  // 拷贝字符串到数组
    cout << "string: " << add << endl;

    // 创建一个 shared_ptr 管理的 int 变量
    shared_ptr<int> p = make_share_array<int>(1);
    *p = 100;
    cout << *p.get() << "  " << *p << endl;

    return 0;
}

在这里插入图片描述

独占智能指针

unique_ptr的初始化

#include <iostream>
#include <memory>
using namespace std;

class Test
{
public:
	Test(int i)	{}
	void print() { cout << "i: " << i << endl; }
private:
	int i;
};

int main()
{
	//通过构造函数初始化	
	unique_ptr<int> ptr1(new int(9));
	//unique_ptr<int> ptr2 = ptr1;//err >>>>> 独占拥有这段内存
	//通过移动构造函数初始化
	unique_ptr<int> ptr2 = move(ptr1);
	//通过reset初始化
	ptr2.reset(new int(8));
	//获取原始指针
	unique_ptr<Test> ptr3(new Test(1));
	Test* pt = ptr3.get();
	pt->print();

	ptr3->print();
}

在这里插入图片描述

unique_ptr删除器

unique_ptr指定删除器和shared_ptr指定删除器是有区别的,unique_ptr指定删除器的时候需要确定删除器的类型,所以不能像shared_ptr那样直接指定删除器,举例说明:

//传入的删除器的类型为ptrFunc >>>>>>>>> []---> 可以看作函数指针
using ptrFunc = void(*)(Test*);
unique_ptr<Test, ptrFunc> ptr4(new Test("hello"), [](Test* t) {
	delete t;
});

//后面的lambda表达是仿函数因此需要进行包装
unique_ptr<Test, function<void(Test*)>> ptr4(new Test("hello"), [=](Test* t) {
	delete t;
});

//独占的智能指针可以管理数组类型的地址,能够自动释放
unique_ptr<Test[]>ptr5(new Test[3]);
//c++11以后,共享的智能指针可以管理数组类型的地址,能够自动释放
shared_ptr<Test[]>ptr6(new Test[3]);

补充

// 直接使用 lambda 表达式作为删除器类型
auto deleter = [](Test* t) { delete t; };
unique_ptr<Test, decltype(deleter)> ptr4(new Test("hello"), deleter);

弱引用智能指针

弱引用智能指针std::weak_ptr可以看做是shared_ptr的助手,它不管理shared_ptr内部的指针。std::weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在。

weak_ptr初始化

#include <iostream>
#include <memory>
using namespace std;

int main() 
{
    shared_ptr<int> sp(new int);

    weak_ptr<int> wp1;
    weak_ptr<int> wp2(wp1);
    weak_ptr<int> wp3(sp);
    weak_ptr<int> wp4;
    wp4 = sp;
    weak_ptr<int> wp5;
    wp5 = wp3;
    
    return 0;
}

use_count()

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    shared_ptr<int> sp(new int);

    weak_ptr<int> wp1;
    weak_ptr<int> wp2(wp1);
    weak_ptr<int> wp3(sp);
    weak_ptr<int> wp4;
    wp4 = sp;
    weak_ptr<int> wp5;
    wp5 = wp3;

    cout << "use_count: " << endl;
    cout << "wp1: " << wp1.use_count() << endl;
    cout << "wp2: " << wp2.use_count() << endl;
    cout << "wp3: " << wp3.use_count() << endl;
    cout << "wp4: " << wp4.use_count() << endl;
    cout << "wp5: " << wp5.use_count() << endl;
    return 0;
}

在这里插入图片描述
通过打印的结果可以知道,虽然弱引用智能指针wp3、wp4、wp5监测的资源是同一个,但是它的引用计数并没有发生任何的变化,也进一步证明了weak_ptr只是监测资源,并不管理资源。

expired()

通过调用std::weak_ptr类提供的expired()方法来判断观测的资源是否已经被释放

#include <iostream>
#include <memory>
using namespace std;

int main() 
{
    shared_ptr<int> shared(new int(10));
    weak_ptr<int> weak(shared);
    cout << "1. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;

    shared.reset();
    cout << "2. weak " << (weak.expired() ? "is" : "is not") << " expired" << endl;

    return 0;
}

在这里插入图片描述

lock()

通过调用std::weak_ptr类提供的lock()方法来获取管理所监测资源的shared_ptr对象

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    shared_ptr<int> sp1, sp2;
    weak_ptr<int> wp;

    sp1 = std::make_shared<int>(520);
    wp = sp1;
    sp2 = wp.lock();//wp2=wp1
    cout << "use_count: " << wp.use_count() << endl;

    sp1.reset();
    cout << "use_count: " << wp.use_count() << endl;

    sp1 = wp.lock();
    cout << "use_count: " << wp.use_count() << endl;

    cout << "*sp1: " << *sp1 << endl;
    cout << "*sp2: " << *sp2 << endl;

    return 0;
}

在这里插入图片描述

reset()

通过调用std::weak_ptr类提供的reset()方法来清空对象,使其不监测任何资源

#include <iostream>
#include <memory>
using namespace std;

int main() 
{
    shared_ptr<int> sp(new int(10));
    weak_ptr<int> wp(sp);
    cout << "1. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;

    wp.reset();
    cout << "2. wp " << (wp.expired() ? "is" : "is not") << " expired" << endl;

    return 0;
}

在这里插入图片描述

共享智能指针的注意事项

不能使用一个原始地址初始化多个共享智能指针

在这里插入图片描述

#include <iostream>
#include <memory>
using namespace std;

struct Test
{
    shared_ptr<Test> getSharedPtr()
    {
        return shared_ptr<Test>(this);
    }

    ~Test()
    {
        cout << "class Test is disstruct ..." << endl;
    }

};

int main()
{
    Test* t = new Test;
    //shared_ptr<Test> ptr1(t);
    //shared_ptr<Test> ptr2(t);
    shared_ptr<Test> ptr1(t);
    shared_ptr<Test> ptr2(ptr1);

    return 0;
}

函数不能返回管理this的共享智能指针对象

在这里插入图片描述
//err

#include <iostream>
#include <memory>
using namespace std;

struct Test
{
    shared_ptr<Test> getSharedPtr()
    {
        return shared_ptr<Test>(this);//err
    }

    ~Test()
    {
        cout << "class Test is disstruct ..." << endl;
    }

};

int main()
{
    shared_ptr<Test> sp1(new Test);
    cout << "use_count: " << sp1.use_count() << endl;
    //这里的this指向的是Test对象 相当于此时的sp2和sp1指向同一个内存
    shared_ptr<Test> sp2=sp1->getSharedPtr();
    cout << "use_count: " << sp1.use_count() << endl;
    return 0;
}

解决方案

这个问题可以通过weak_ptr来解决,通过wek_ptr返回管理this资源的共享智能指针对象shared_ptr。C++11中为我们提供了一个模板类叫做std::enable_shared_from_this,这个类中有一个方法叫做shared_from_this(),通过这个方法可以返回一个共享智能指针,在函数的内部就是使用weak_ptr来监测this对象,并通过调用weak_ptr的lock()方法返回一个shared_ptr对象

#include <iostream>
#include <memory>
using namespace std;

struct Test : public enable_shared_from_this<Test>
{
    shared_ptr<Test> getSharedPtr()
    {
        return shared_from_this();
    }
    ~Test()
    {
        cout << "class Test is disstruct ..." << endl;
    }
};

int main()
{
    shared_ptr<Test> sp1(new Test);// >>>>>>>>>> 此时基类的weak_ptr被初始化了
    cout << "use_count: " << sp1.use_count() << endl;
    shared_ptr<Test> sp2 = sp1->getSharedPtr();
    cout << "use_count: " << sp1.use_count() << endl;
    return 0;
}

在这里插入图片描述
智能指针内部使用了引用计数(std::shared_ptr)或独占所有权(std::unique_ptr)的机制来管理对象的生命周期。std::shared_ptr 通过引用计数来管理资源。当用一个 std::shared_ptr 初始化另一个 std::shared_ptr 时,新的智能指针将增加该资源的引用计数,而不是直接复制资源。这意味着多个 shared_ptr 可以共享同一个资源,直到最后一个 shared_ptr 被销毁时才释放资源。
在调用enable_shared_from_this类的shared_from_this()方法之前,必须要先初始化函数内部weak_ptr对象,否则该函数无法返回一个有效的shared_ptr对象

共享智能指针不能循环引用

#include <iostream>
#include <memory>
using namespace std;

struct TA;
struct TB;

struct TA
{
    shared_ptr<TB> bptr;
    ~TA()
    {
        cout << "class TA is disstruct ..." << endl;
    }
};

struct TB
{
    shared_ptr<TA> aptr;
    ~TB()
    {
        cout << "class TB is disstruct ..." << endl;
    }
};

void testPtr()
{
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->bptr = bp;
    bp->aptr = ap;
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;
}

int main()
{
    testPtr();
    return 0;
}

从上面的打印结果来看没有调用析构因此是有问题的,因为 TA 和 TB 之间形成了循环引用,当 testPtr() 函数结束时,局部变量 ap 和 bp 会被销毁,这意味着它们会从栈上被移除。但是,ap 和 bp 持有的 shared_ptr 仍然存在对 TA 和 TB 对象的引用,因此它们的引用计数依然是 2。即使 testPtr 函数结束,但是由于引用计数没有降为 0,它们的析构函数不会被调用,导致内存泄漏。
在这里插入图片描述
解决方案:

#include <iostream>
#include <memory>
using namespace std;

struct TA;
struct TB;

struct TA
{
    weak_ptr<TB> bptr;
    ~TA()
    {
        cout << "class TA is disstruct ..." << endl;
    }
};

struct TB
{
    shared_ptr<TA> aptr;
    ~TB()
    {
        cout << "class TB is disstruct ..." << endl;
    }
};

void testPtr()
{
    shared_ptr<TA> ap(new TA);
    shared_ptr<TB> bp(new TB);
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;

    ap->bptr = bp;
    bp->aptr = ap;
    cout << "TA object use_count: " << ap.use_count() << endl;
    cout << "TB object use_count: " << bp.use_count() << endl;
}

int main()
{
    testPtr();//>>>>>这样后当testPtr()函数结束后引用计数就会变成0
    return 0;
}

在这里插入图片描述

分析TA 对象的引用计数变为 2 是因为 shared_ptr (ap) 和 shared_ptr (bp->aptr) 两者同时持有 TA 的引用。
当 bp 被销毁时,bp->aptr(即 shared_ptr)仍然持有对 TA 的引用,直到 ap 也被销毁。
在 testPtr() 函数结束时,ap 会被销毁,TA 的引用计数变为 0,触发 TA 对象的析构。

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

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

相关文章

锐捷技能大赛—L2TP隧道与L2TP Over IPSec嵌套,并在隧道内运行OSPF

目录 总部与分支站点之间建立隧道 基础配置 配置L2TP VPN ​编辑配置L2TP Over IPSec L2TP Over IPSec隧道内运行OSPF协议 总部与分支站点之间建立隧道 拓扑如下 基础配置 R1 int g0/1 ip add 10.1.1.1 30 int g0/0 ip add 192.168.10.254 24 exit ip route 0.0.0.0 0.0…

python可视化将多张图整合到一起(画布)

这周有点事忙着&#xff0c;没时间重温刚结束的Mathurcup数学建模&#xff0c;这两天也是再看了下&#xff0c;论文还是赶紧挺烂的&#xff0c;但比国赛又有进步&#xff08;说起国赛又不得不抱怨了&#xff0c;基本其余省份都发了&#xff0c;但江西......哎&#xff09;。哎&…

网络编程、UDP、TCP、三次握手、四次挥手

一、初识网络编程 网络编程的概念&#xff1a;在网络通信协议下&#xff0c;不同计算机上运行的程序&#xff0c;进行的数据传输。 应用场景&#xff1a;即时通信、网游对战、金融证券、国际贸易、邮件等等。 不管是什么场景&#xff0c;都是计算机和计算机之间通过网络进行…

在 Jupyter Notebook 中使用 Matplotlib 进行交互式可视化的教程

在 Jupyter Notebook 中使用 Matplotlib 进行交互式可视化的教程 引言 数据可视化是数据分析的重要组成部分&#xff0c;能够帮助我们更直观地理解数据。Matplotlib 是 Python 中最流行的绘图库之一&#xff0c;而 Jupyter Notebook 则是进行数据分析和可视化的理想环境。本文…

[单例模式]

[设计模式] 设计模式是软件工程中的一种常见做法, 它可以理解为"模板", 是针对一些常见的特定场景, 给出的一些比较好的固定的解决方案. 不同语言适用的设计模式是不一样的. 这里我们接下来要谈到的是java中典型的设计模式. 而且由于设计模式比较适合有一定编程经…

STM32软件开发 —— STM32CudeMX使用优点

目 录 STM32CudeMX使用思路步骤详细 STM32CudeMX 在图形化工具STM32CudeMX出现之前&#xff0c;开发者通常是参考库驱动文件中的例程来配置芯片的&#xff0c;进行拷贝和修改等&#xff0c;为了提高开发效率&#xff0c;ST公司开发了STM32CudeMX工具&#xff0c;通过它简化了芯…

江西省补贴性线上职业技能培训管理平台(刷课系统)

江西省补贴性线上职业技能培训管理平台(刷课系统) 目的是为了刷这个网课 此系统有两个版本一个是脚本运行&#xff0c;另外一个是可视化界面运行 可视化运行 技术栈:flask、vue3 原理: 通过分析网站接口&#xff0c;对某些接口加密的参数进行逆向破解&#xff0c;从而修改请求…

Golang | Leetcode Golang题解之第546题移除盒子

题目&#xff1a; 题解&#xff1a; func removeBoxes(boxes []int) int {dp : [100][100][100]int{}var calculatePoints func(boxes []int, l, r, k int) intcalculatePoints func(boxes []int, l, r, k int) int {if l > r {return 0}if dp[l][r][k] 0 {r1, k1 : r, k…

es自动补全(仅供自己参考)

elasticssearch提供了CompletionSuggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询效率&#xff0c;对于文档中字段的类型有一些约束&#xff1a; 查询类型必须是&#xff1a;completion 字段内容是多个补全词条形成的数组 P…

ANDROIDWORLD: A Dynamic Benchmarking Environment for Autonomous Agents论文学习

这个任务是基于androidenv的。这个环境之前学过&#xff0c;是一个用来进行强化学习的线上环境。而这篇文章的工作就是要给一些任务加上中间的奖励信号。这种训练环境的优点就是动态&#xff0c;与静态的数据集&#xff08;比如说我自己的工作&#xff09;不同&#xff0c;因此…

VBA10-处理Excel的动态数据区域

一、end获取数据边界 1、基本语法 1-1、示例&#xff1a; 2、配合row和column使用 2-1、示例1 2-2、示例2 此时&#xff0c;不管这个有数值的区域&#xff0c;怎么增加边界&#xff0c;对应的统计数据也会跟着变的&#xff01; 二、end的缺陷 若是数据区域不连贯&#xff0c;则…

【FFmpeg】FFmpeg 函数简介 ③ ( 编解码相关函数 | FFmpeg 源码地址 | FFmpeg 解码器相关 结构体 和 函数 )

文章目录 一、FFmpeg 解码器简介1、解码流程分析2、FFmpeg 编解码器 本质3、FFmpeg 编解码器 ID 和 名称 二、FFmpeg 解码器相关 结构体 / 函数1、AVFormatContext 结构体2、avcodec_find_decoder 函数 - 根据 ID 查找 解码器3、avcodec_find_decoder_by_name 函数 - 根据 名称…

Linux完结

学习视频笔记均来自B站UP主" 泷羽sec",如涉及侵权马上删除文章 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 【linux基础之病毒编写&#xff08;完结&#xff09;】 https://www.bilibili.com/video…

分享三个python爬虫案例

一、爬取豆瓣电影排行榜Top250存储到Excel文件 近年来&#xff0c;Python在数据爬取和处理方面的应用越来越广泛。本文将介绍一个基于Python的爬虫程序&#xff0c;用于抓取豆瓣电影Top250的相关信息&#xff0c;并将其保存为Excel文件。 获取网页数据的函数&#xff0c;包括以…

【计网】数据链路层笔记

【计网】数据链路层 数据链路层概述 数据链路层在网络体系结构中所处的地位 链路、数据链路和帧 链路(Link)是指从一个节点到相邻节点的一段物理线路(有线或无线)&#xff0c;而中间没有任何其他的交换节点。 数据链路(Data Link)是基于链路的。当在一条链路上传送数据时&a…

docker 拉取MySQL8.0镜像以及安装

目录 一、docker安装MySQL镜像 搜索images 拉取MySQL镜像 二、数据挂载 在/root/mysql/conf中创建 *.cnf 文件 创建容器,将数据,日志,配置文件映射到本机 检查MySQL是否启动成功&#xff1a; 三、DBeaver数据库连接 问题一、Public Key Retrieval is not allowed 问题…

【c++篇】:栈、队列、优先队列:容器世界里的秩序魔法 - stack,queue与priority_queue探秘

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;c篇–CSDN博客 文章目录 前言一.容器stack1.介绍2.成员函数3.模拟实现4.注意事项 二.容器qu…

实现uniapp-微信小程序 搜索框+上拉加载+下拉刷新

pages.json 中的配置 { "path": "pages/message", "style": { "navigationBarTitleText": "消息", "enablePullDownRefresh": true, "onReachBottomDistance": 50 } }, <template><view class…

无人机培训机型有哪些?CAAC考证选3类还是4类

无人机培训是一个涵盖多个方面的综合性过程&#xff0c;旨在培养具备无人机操作技能和相关知识的人才。 无人机培训机型 无人机培训通常涵盖多种机型&#xff0c;以满足不同领域和应用场景的需求。常见的无人机培训机型包括&#xff1a; 1. 多旋翼无人机&#xff1a;也称为多…

95.【C语言】数据结构之双向链表的头插,头删,查找,中间插入,中间删除和销毁函数

目录 1.双向链表的头插 方法一 方法二 2.双向链表的头删 3.双向链表的销毁 4.双向链表的某个节点的数据查找 5.双向链表的中间插入 5.双向链表的中间删除 6.对比顺序表和链表 承接94.【C语言】数据结构之双向链表的初始化,尾插,打印和尾删文章 1.双向链表的头插 方法…