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++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

c++中的set容器介绍及操作大全

《c++中的set容器介绍及操作大全》:本文主要介绍c++中的set容器介绍及操作大全,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录​​一、核心特性​​️ ​​二、基本操作​​​​1. 初始化与赋值​​​​2. 增删查操作​​​​3. 遍历方

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

C++11委托构造函数和继承构造函数的实现

《C++11委托构造函数和继承构造函数的实现》C++引入了委托构造函数和继承构造函数这两个重要的特性,本文主要介绍了C++11委托构造函数和继承构造函数的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、委托构造函数1.1 委托构造函数的定义与作用1.2 委托构造函数的语法1.3 委托构造函

C++11作用域枚举(Scoped Enums)的实现示例

《C++11作用域枚举(ScopedEnums)的实现示例》枚举类型是一种非常实用的工具,C++11标准引入了作用域枚举,也称为强类型枚举,本文主要介绍了C++11作用域枚举(ScopedEnums... 目录一、引言二、传统枚举类型的局限性2.1 命名空间污染2.2 整型提升问题2.3 类型转换问题三、C