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++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

2024/9/8 c++ smart

1.通过自己编写的class来实现unique_ptr指针的功能 #include <iostream> using namespace std; template<class T> class unique_ptr { public:         //无参构造函数         unique_ptr();         //有参构造函数         unique_ptr(