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++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

C++ STL-string类底层实现过程

《C++STL-string类底层实现过程》本文实现了一个简易的string类,涵盖动态数组存储、深拷贝机制、迭代器支持、容量调整、字符串修改、运算符重载等功能,模拟标准string核心特性,重点强... 目录实现框架一、默认成员函数1.默认构造函数2.构造函数3.拷贝构造函数(重点)4.赋值运算符重载函数

C++ vector越界问题的完整解决方案

《C++vector越界问题的完整解决方案》在C++开发中,std::vector作为最常用的动态数组容器,其便捷性与性能优势使其成为处理可变长度数据的首选,然而,数组越界访问始终是威胁程序稳定性的... 目录引言一、vector越界的底层原理与危害1.1 越界访问的本质原因1.2 越界访问的实际危害二、基

c++日志库log4cplus快速入门小结

《c++日志库log4cplus快速入门小结》文章浏览阅读1.1w次,点赞9次,收藏44次。本文介绍Log4cplus,一种适用于C++的线程安全日志记录API,提供灵活的日志管理和配置控制。文章涵盖... 目录简介日志等级配置文件使用关于初始化使用示例总结参考资料简介log4j 用于Java,log4c

C++归并排序代码实现示例代码

《C++归并排序代码实现示例代码》归并排序将待排序数组分成两个子数组,分别对这两个子数组进行排序,然后将排序好的子数组合并,得到排序后的数组,:本文主要介绍C++归并排序代码实现的相关资料,需要的... 目录1 算法核心思想2 代码实现3 算法时间复杂度1 算法核心思想归并排序是一种高效的排序方式,需要用

C++11范围for初始化列表auto decltype详解

《C++11范围for初始化列表autodecltype详解》C++11引入auto类型推导、decltype类型推断、统一列表初始化、范围for循环及智能指针,提升代码简洁性、类型安全与资源管理效... 目录C++11新特性1. 自动类型推导auto1.1 基本语法2. decltype3. 列表初始化3

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么