存档

文章标签 ‘c++’

C++17 新特性:结构化绑定

2024/03/29 1,004

结构化绑定定义及用法

所谓"结构化绑定", 即将指定的名称绑定到初始化器的子对象或元素上。比如有如下结构体:

那么有如下写法,直接把该结构体的成员绑定到新的变量名上:

结构化绑定支持的方式:

auto 前后可以使用 const alignas& 修饰。

结构化绑定可以用在 数组(array)、类元组(tuple-like)和成员变量上(data members)。

这么做的好处是使得代码结构更清晰,简洁易读。

继续阅读

C++20 Ranges

2024/01/28 904

Ranges 是C++20 提供的一套对范围的统一抽象和操作库。ranges 指可迭代的序列,它可以包括任何能够提供迭代器的数据结构, 如 vector, list, etc. 引入 ranges 可以使迭代的处理更简洁直观灵活。

我们知道 STL algorithms 利用迭代器对数据进行操作。比如我们需要对一个 vector 进行排序, 需要将排序的范围的迭代器做为参数传递给 sort() 方法:

这种写法很灵活。但更多的时候,我们是想对整个 vector 进行排序,传入迭代器反而是多余的操作了。引入 Ranges 即可简化这一操作。

在 ranges 库中,默认是对整个范围进行操作。当然,也可以像原来一样,使用迭代器来指定范围:

这是一种简化操作的方式。但 ranges 更重要的优势在于,它允许你以函数式编程的方式来操作 STL algorithm 。

继续阅读

C++ 构造函数漫谈(四)

2019/08/21 3,270

这一章谈 C++11 中引入的两种 “语法糖” .使用它们可以使得我们的代码更为简洁优雅。

委托构造函数

在同一个类中,一个构造函数可以调用另一个构造函数,这叫委托构造函数。这是 C++ 11 的新特性。
委托构造函数可以简化在每个构造函数中的重复代码。

注意一点,委托构造函数在使用时不可以形成环:禁止套娃。

继续阅读

C++ 构造函数漫谈(三)

2019/08/19 3,323

这一章聊一聊在面向对象的C++中,构造函数的调用顺序。

数据成员的构造顺序

一个类的数据成员的初始化顺序只与其在类中的声明顺序相关,与其它无关。

而析构时,如果成员是在堆中,析构顺序正好与构造时相反。

类A的成员的构造顺序为: m1_, m2_, pm1_, pm2_ 。析构时的顺序为 m2_, m1_ ,由于 pm1_, pm2_ 不在堆中,所以它们的析构需要类A自己管理。

继续阅读

C++ 构造函数漫谈(二)

2019/08/18 3,652

三五零法则

我们知道,编译器会为类自动生成几个特别的成员函数:构造函数、复制构造函数、复制赋值运算符、析构函数。后三者比较特殊,我们在下面会频繁提到。

三法则

若一个类需要用户显式定义 析构函数、复制构造函数、复制赋值运算符 中的一个,那么这三个函数都需要显式定义 。如果用户显式定义了其中一个,另外两个还是会被编译器隐式定义,这种混杂的情况容易生产无法预期的错误。
如果一个类中有非基本数据类型或者非类类型的成员(如指针、文件描述符等),则这一法则表现的更为明显:隐式析构函数无法对这种成员进行有效的释放,隐式复制构造函数和隐式复制赋值运算符无法进行深拷贝。

继续阅读

C++ 构造函数漫谈(一)

2019/08/12 4,155

C++ 构造函数有很多有意思的小细节。这里来做一些探讨。这些内容可能会分为几章,这一章来探讨 隐式构造函数,显式空构造函数 和 =default 修饰的构造函数 ,私有构造函数和 =delete 修饰的构造函数 之间的区别。

在开始之前,我们先了解两种特殊的类:

聚合类 与 POD

聚合类 是 C++ 中的一个特殊的类型。当一个类(class, struct, union) 满足以下条件时,它是一个聚合类:

  • 无显式声明的构造函数(可以是 defaultdelete 的)
  • 无基类
  • 无虚成员函数
  • 无私有的或受保护的非静态数据成员
  • 无使用 {}= 直接初始化的非静态数据成员

一个普通数组也是一种聚合类型(如 int[10], char[], double[2][3])

POD ( Plain old data structure ) 则是一种特殊的聚合类,它必须满足聚合类的所有条件,且不具有以下成员:

  • 指针到成员类型的非静态数据成员(包括数组)。
  • 非POD类类型的非静态数据成员(包括数组)。
  • 引用类型的(reference type)非静态数据成员。
  • 用户定义的拷贝与赋值算子。
  • 用户定义的析构函数。

可见,POD类类型就是指class、struct、union,且不具有用户定义的构造函数、析构函数、拷贝算子、赋值算子;不具有继承关系,因此没有基类;不具有虚函数,所以就没有虚表;非静态数据成员没有私有或保护属性的、没有引用类型的、没有非POD类类型的(即嵌套类都必须是POD)、没有指针到成员类型的(因为这个类型内含了this指针)

POD 一般用来在不同的模块之前传递数据使用。如一个 C++ 库向外提供 C 接口,可以使用 POD 作为参数。

隐式构造函数,显式空构造函数 和 =default 修饰的构造函数。

对于 未定义任何构造函数 的类型( struct class or union),编译器会为该为自动生成一个 inline public 的构造函数, 如果这个类型满足 constexpr 类型的要求,则这个构造函数还会被 constexpr 修饰,这个由编译器生成的构造函数,我们称之为 隐式构造函数 或 默认构造函数。在 C++11 以前,如果用户声明了其它构造函数,则编译器不会生成默认构造函数,需要我们显式的声明。而在 C++11 以后,我们仍可用 default 关键字来强制编译器自动生成原本隐式声明的默认构造函数。

继续阅读

关于 enable_shared_from_this

2019/05/16 5,245

一. 引入

简单地说: enable_shared_from_this 是为了解决 在类的内部获取自己的 shared_ptr 这件事情而存在的。

众所周知, 每一个对象都能通过this 指针来访问自己的地址。this 指针也是所有成员函数的隐含参数。然而有些时候,我们需要的不仅是 this,而是一个 "this的智能指针"。

这里有一个常见的场景:

代码如上:在异步方法 DoSth_Async() 中调用了成员方法 OnDo(bool) . 这里存在一个问题: 当 OnDo() 被调用的时候,该类的是否还在生存中:

智能指针 ptr 在出作用域后立即被释放。所以当 OnDo() 被调用的时候,其所在的对象实际已经被释放了。如果确保在 OnDo() 被调用的时候,该对象仍然在生命周期内呢?一个方便的方法便上在构建线程的时候,将该对象的 shared_ptr 传入到线程。在该线程的生命周期内,该对象就会一直存在。这是一种利用 shared_ptr 的 保活机制

继续阅读

多线程编程中的一些原则

2018/03/19 4,851

关于 C++ 多线程编程一的些基本知识可以参考本博客的《C++11/14 新特性(多线程)》 ,《Unix线程基础》。本章不是多线程编程教程,而是个人经验的一些总结。这些经验有一些可能是不正确的,希望在今后的编程中实践、改进。

线程同步的四项基本原则:

  1. 最低限度地共享对象。对象尽量不要暴露给别的线程,如果需要暴露,优先考虑 immutable对象。否则尽量使用同步措施来充分地保护它
  2. 尽量使用高级地并发编程构件,如 任务队列、生产者消费者模式等
  3. 只用非递归的互斥器和条件变量,慎用读写锁,尽量少用信号量
  4. 除了使用 atomic 整数外,不要自己编写 lock-free 代码,也不要用"内核级"同步原语

互斥器 Mutex

mutex 是最常用的同步原语,它保护一个临界区,任何时候最多只能有一个线程能够访问 mutex 保护的域。使用 mutex 主要是为了保护共享数据。一般原则有:

  • 使用 RAII手法封装 mutex 的创建、销毁、加锁、解锁操作,充分保证锁的有效期等于其作用域,而不会因为中途返回或异常而忘记解锁。这类似于 Java 的synchronized 或 C# 的 using 语句。
  • 使用非递归的 mutex
  • 尽量不要人为地调用 lock()unlock()函数,将这些操作交给栈上的 guard 对象,利用其构造与析构函数。
  • 不要跨线程地加解锁,避免在不同的函数中分别加锁\解锁,避免在不同的语句分支中加锁\解锁
  • 每当构造 guard 对象时,需要考虑栈上已有的锁,防止因加锁顺序不同而导致死锁
  • 避免跨进程的 mutex, 进程间通讯尽量使用 TCP sockets

只使用非递归地 mutex

继续阅读