Modern C++ 新特性

2024-05-10 14:48
文章标签 c++ 特性 modern

本文主要是介绍Modern C++ 新特性,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

智能指针

weak_ptr

  1. 一般配合shared_ptr使用,两者之间可以相互转换:
  • shared_ptr转成weak_ptr:可以直接赋值,不会增加引用计数,weak_ptr有自己的引用计数:副引用计数
	auto sp = std::make_shared<int>(42);std::weak_ptr<int> gw = sp;
  • weak_ptr转成shared_ptr,用以测试shared_ptr指向的对象是否过期,该操作是原子的:
	std::shared_ptr<Widget> spw1 = wpw.lock(); //如果wpw已经过期 //spw1的值是null auto spw2 = wpw.lock(); //结果同上,这里使用了auto

eg1:

#include <iostream>
#include <memory>std::weak_ptr<int> gw;void observe()
{std::cout << "use_count == " << gw.use_count() << ": ";if (auto spt = gw.lock()) { // Has to be copied into a shared_ptr before usagestd::cout << *spt << "\n";}else {std::cout << "gw is expired\n";}
}int main()
{{auto sp = std::make_shared<int>(42);gw = sp;observe();}observe();
}

result:
在这里插入图片描述
eg2:
另外一种方式是以 std::weak_ptr 为参数,使用 std::shared_ptr 构造函数。这种情况下,如果 std::weak_ptr 指向的对象过期的话,会有异常抛出:

//如果wpw过期的话,抛出std::bad_weak_ptr异常
std::shared_ptr<Widget> spw3(wpw);
  1. 当两个对象存在相互引用的时候,推荐使用weak_ptr,不然会出现内存泄漏。 这是因为weak_ptr不会影响到引用计数;
    eg:
class ClassB;class ClassA
{
public:ClassA() { cout << "ClassA Constructor..." << endl; }~ClassA() { cout << "ClassA Destructor..." << endl; }weak_ptr<ClassB> pb;  // 在A中引用B
};class ClassB
{
public:ClassB() { cout << "ClassB Constructor..." << endl; }~ClassB() { cout << "ClassB Destructor..." << endl; }weak_ptr<ClassA> pa;  // 在B中引用A
};int main() {shared_ptr<ClassA> spa = make_shared<ClassA>();shared_ptr<ClassB> spb = make_shared<ClassB>();spa->pb = spb;spb->pa = spa;// 函数结束,思考一下:spa和spb会正确释放资源
}

weak_ptr循环引用的资源可以得到正常释放,因为weak_ptr不会增加引用计数,如果使用shared_ptr的话,会导致生命周期结束时,引用计数无法减到0,从而导致内存泄漏;

unique_ptr

std::unique_ptr 是一个move-only类型,不能拷贝(拷贝构造和赋值运算符函数是被delete掉的),独占资源;

eg1:不支持拷贝和赋值:

unique_ptr<int> up1(new int());    // okay,直接初始化
unique_ptr<int> up2 = new int();   // 编译error! 构造函数是explicit(不允许隐式转换)
unique_ptr<int> up3(up1);          // 编译error! 不允许拷贝

那么如何传递unique_ptr呢?

  • 通过引用传递,引用传递需要注意的是对象的生命周期,引用传递不会改变引用计数;
  • 移动构造
  • 通过release转移所有权:调用release 会切断unique_ptr 和它原来管理的对象的联系。release 返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。如果不用另一个智能指针或者原始指针来保存release返回的指针,就会造成对象丢失,内存泄漏。
// 传入参数
// 传unique_ptr参数可以使用引用避免所有权的转移
// 这里传入的引用,并非拷贝
void func1(unique_ptr<int> &up)
{cout<<*up<<endl;
}
//使用up作为参数
unique_ptr<int> up(new int(10));
//传引用,不涉及所有权的转移
func1(up);unique_ptr<int> func2(unique_ptr<int> up)
{cout<<*up<<endl;return up;  // 转移所有权
}
//暂时转移所有权,函数结束时重新收回所有权
//如果不用up重新接受func2的返回值,这块内存就泄漏了 
up = func2(unique_ptr<int> (up.release()));
// 或者使用移动构造
up = func2(std::move(up));

eg3: 转移所有权

up.release() 
up放弃对它所指对象的控制权,并返回保存的指针,将up置为空,不会释放内存up.reset(…) 
参数可以为空,返回值是void。先将up所指对象释放,然后重置up所指对象为参数值.会释放内存// 将所有权从p1转移给p2
unique_ptr<string> p2(p1.release()); // release将p1置为空
unique_ptr<string> p3(new string("C++"));
// 将所有权从p3转移给p2
p2.reset(p3.release());  // reset释放了p2原来指向的内存

注: 不要与裸指针混用
unique_ptr不允许两个独占指针指向同一个对象,在没有裸指针的情况下,我们只能用release获取内存的地址,同时放弃对对象的所有权,这样就有效避免了多个独占指针同时指向一个对象。 而使用裸指针就很容器打破这一点

eg4:

int *x(new int());
unique_ptr<int> up1,up2;
//会使up1 up2指向同一个内存
up1.reset(x);
up2.reset(x);

闭包

闭包:与函数A调用函数B相比较,闭包中函数A调用函数B,可以不通过函数A给函数B传递函数参数,而使函数B可以访问函数A的上下文环境才可见(函数A可直接访问到)的变量;比如:

函数B(void) {

}

函数A {
int a = 10;
B(); //普通调用函数B
}

函数B无法访问a;但如果是按闭包的方式,则可以访问变量a:
函数A() {
int a = 10;
auto closure_B = a {

}
}
所以闭包的优缺点很清晰,都是同一个:可以不通过传参获取调用者的上下文环境;
在c++中,可以通过lambda表达式和std::bind实现闭包

lambda表示式

具有如下形式:
[捕获列表](参数)-> 返回类型 {函数体}
关于捕获列表:捕获调用者上下文环境的需要访问的变量,可按值捕获或按引用捕获,其规则如下:

	[]:什么也不捕获[=]:捕获所有一切变量,都按值捕获[&]:捕获所有一切变量,都按引用捕获[=, &a]:捕获所有一切变量,除了a按引用捕获,其余都按值捕获[&, =a]:捕获所有一切变量,除了a按值捕获,其余都按引用捕获[a]:只按值捕获a[&a]:只按引用捕获a[a, &b]:按值捕获a,按引用捕获b关于参数列表:和函数调用的参数列表含义完全一样;

注:

  1. 可以直接使用局部static变量和在它所在的函数之外声明的名字;
  2. mutable:必须注意加入mutable可以修改的是:在lambda匿名函数体里边,按值捕获到的变量,实质上是调用者函数中变量的只读拷贝(read-only),加入了mutable后,匿名函数体内部可以修改这个拷贝的值,也就是说调用者函数中该变量的值依然不会被改变
    eg1:
int v1 =42;
// f可以改变它所捕获的变量
auto f=[v1]() mutable {return ++v1;};
v1 = 0;
auto j = f(); // j = 43
  1. 如果一个lambda体包含了return之外的任何语句,则编译器默认推断此lambda返回void。与其他返回void的函数类似,被推断返回void的lambda不能返回值。如果需要返回其他值,则需要显式指定返回类型:
    eg2:
// 错误,不能推断lambda的返回类型
transform(vi.begin(), vi.end(), vi.begin(), [](int i){ if (i < 0)return -i;else return i;
});
// 正确写法
transform(vi.begin(), vi.end(), vi.begin(), [](int i) -> int { if (i < 0)return -i;else return i;
});

function与bind

  • function是一个class template,可调用对象,通过模块参数,指定对象的调用形式:
int add(int i, int j) {return i + j}
// 函数对象类
struct divide {int operator() (int i, int j) {return i / j;}
}
function<int (int, int)> f1 = add; //  函数指针
function<int (int, int)> f2 = divide(); // 函数对象类的对象
function<int (int, int)> f3 = [](int i, int j) {return i * j;}; // lambda表达式
  • bind是一个标准库函数function template,通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原调用对象的参数列表。其一般形式为:
    auto newCallable = bind(callable, arg_list)
    注:arg_list为参数列表,是传递给callable的参数列表,可能会包含_n的参数名(n是一个整数),这些参数被称为占位符,表示newCallable的参数,他们占据了newCallable的参数的“位置”,数值n表示生成的可调用对象的参数位置:_1为newCallable的第一个参数,_2为第二个参数
    eg:
bool check_size(const string &s, string::size_type sz) {return s.size() >= sz;
}
// 新的可调用对象 bind普通函数
auto check6 = bind(check_size, std::placeholders::_1, 6);// 绑定成员函数
#include <iostream>
#include <functional>class MyClass
{
public:MyClass();~MyClass();void func1(int a, int b, int &sum) {sum = a + b; std::cout << "int func 1 " << sum << std::endl;}void func2(int a, int b){int sum = 0;// 成员函数的第一个参数为this指针auto f = std::bind(&MyClass::func1, this, a, b, sum);f();}
};MyClass::MyClass()
{
}MyClass::~MyClass()
{
}int main() {MyClass test;test.func2(1, 32);system("pause");
}// 运行结果如下
int func 1 33
请按任意键继续. . .

这篇关于Modern C++ 新特性的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/976751

相关文章

C++一个数组赋值给另一个数组方式

《C++一个数组赋值给另一个数组方式》文章介绍了三种在C++中将一个数组赋值给另一个数组的方法:使用循环逐个元素赋值、使用标准库函数std::copy或std::memcpy以及使用标准库容器,每种方... 目录C++一个数组赋值给另一个数组循环遍历赋值使用标准库中的函数 std::copy 或 std::

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表

使用C++实现单链表的操作与实践

《使用C++实现单链表的操作与实践》在程序设计中,链表是一种常见的数据结构,特别是在动态数据管理、频繁插入和删除元素的场景中,链表相比于数组,具有更高的灵活性和高效性,尤其是在需要频繁修改数据结构的应... 目录一、单链表的基本概念二、单链表类的设计1. 节点的定义2. 链表的类定义三、单链表的操作实现四、

使用C/C++调用libcurl调试消息的方式

《使用C/C++调用libcurl调试消息的方式》在使用C/C++调用libcurl进行HTTP请求时,有时我们需要查看请求的/应答消息的内容(包括请求头和请求体)以方便调试,libcurl提供了多种... 目录1. libcurl 调试工具简介2. 输出请求消息使用 CURLOPT_VERBOSE使用 C