CPP实用编程中的一些小技巧和注意事项

c++中的窄化转换类型

  • 从浮点类型到整型。
  • 从浮点类型到更窄或排名更低的浮点类型,除非要转换的值是 constexpr 并且在目标类型的范围内(即使目标类型不具有存储浮点类型的所有有效数字的精度)数字)。
  • 从整型到浮点类型,除非要转换的值是 constexpr 并且其值可以准确地存储在目标类型中。
  • 从一个整型到另一个不能表示原始类型的所有值的整型,除非要转换的值是 constexpr 并且其值可以准确地存储在目标类型中。这涵盖了从宽到窄的整数转换,以及整数符号转换(有符号到无符号,反之亦然)。

c++哪些地方禁止了窄化转换

  • 列表初始化不允许大括号表达式中值的类型被窄化成需要构造的对象类型
  • 被转换的常量表达式(converted const expression)
    • 比如定义数组时数组的长度,一般为size_t / unsigned long long类型的常量表达式

decltype 和 auto的妙用

  • 单auto会失去对原变量的cv限定和引用,使用*decltype(auto) / auto&&*可以达到目的

列表初始化中的聚合初始化

聚合体的要求

  1. 数组
  2. 不能出现以下情况的类类型(c++17标准)
    • 派生类中含有继承构造函数explicit声明的构造函数
    • 派生类含有虚基类
    • 类中含有非private/protected的非静态数据成员
    • 类中含有虚函数成员

继承构造函数

考虑以下情景,基类定义了若干构造函数分别针对进行若干成员进行初始化,派生类中只有一个成员需要定义,但它却需要重载若干构造函数来调用不同的基类构造函数,这样会造成代码冗余,并且编写起来会让人恼火,为了解决上述情况,我们可以使用using base::base来一次性引入基类的所有构造函数,当用子句初始化派生类对象时,可以达到直接调用基类构造函数的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class base{
public:
base() : a1(2){};
// base(int a): a1(a){}
private:
int a1;
};
class derived:public base{
public:
/*inherited constructors 指的是
如果 using 声明指代了正在定义的类的某个直接基类的构造函数(例如 using Base::Base;),
那么在初始化派生类时,该基类的所有构造函数(忽略成员访问)均对重载决议可见。
虽然标准规定忽略对基类的成员访问,但是继承构造函数必须在派生类中可访问,这样在初始化派生类时,首先调用基类的构造函数,派生类中的各成员
使用默认成员初始化器(如果有的话)进行默认初始化*/
using base::base;
/*如果子类定义了相同签名的构造函数(若未定义,则在继承构造函数的影响下同签名构造函数默认是删除的),则基类的同签名继承构造函数被隐藏*/
derived(int a1):a2(a1){}
// derived(): a2(5){}
int a2;
};

内存对齐

内存对齐的好处:

  • cpu运算时首先通过数据总线取得数据,若数据没有按照线路位宽对齐,则可能需要增加取数的次数
  • dma访问硬盘时,要求数据是4k对齐的

gcc对于内存对齐的扩展支持 - __attribute__属性说明符

  • attribute((aligned(long long)))设置变量的对齐长度为long long的对齐长度

    属性说明符指在为类型、对象、代码等引入由实现定义的属性,在gcc中属性说明符的语法一般为__atribute__(()),c++11引入了属性说明符序列,语法为[[attr]] [[attr1, attr2, attr3(args)]] [[namespace::attr(args)]] alignas-说明符 , 两种属性语法都可以位于声明的对象之后或者位于整个声明序列之前,这些情况下它们被组合起来。大多数其他情形中,属性应用于直接位于其之前的实体(或声明序列中对应的实体)。

    class-key attr(optional) class-head-name
    对于类来说,在class后直接加入属性视为是对整个类加上属性,对于函数来说,在函数名后加入属性视为对该函数实体加上属性

    1
    2
    >void h [[noreturn]] () //OK
    >int q(int i) [[noreturn]] //error, 这里的属性描述符相当于给函数类型

c++11新特性alignas alignof 关键字

  • c++11之前,gcc可以通过__alignof__(int) 获得对齐长度;
    1
    2
    3
    short r1 = 4;
    __attribute__((aligned(8))) short r2 = 4;
    std::cout<<__alignof__(r2)<<std::endl;
  • c++11引入alignof关键字获取类型的对齐长度,不能获取变量的对其长度
    1
    2
    3
    auto x1 = alignof(int);
    int a = 0;
    // auto x2 = alignof(a); error
  • c++11引入alignas设置对齐长度,可用在结构体前设置整个结构体的对齐长度,设置的对齐长度必须大于成员自定义或默认的对齐长度,否则设置无效
1
2
3
4
struct alignas(4) X{ //alignas(4)无效
alignas(double) int x3;
alignas(8) char x4;
}

function wrappers

std::function<F(Args…)> 是一种类型擦除对象。这意味着它擦除了某些操作发生的细节,并为它们提供了统一的运行时接口。对于std::function,主要的操作包括复制/移动、销毁和通过operator()进行“调用”——即“类似函数的调用运算符”。可以包装lambdas,std::bind, 函数指针 & 仿函数

lambdas的复制和默认构造函数是删除的

reference wrappers

std::ref(val) 返回对于一个val的reference wrapper
std::cref(val) 返回一个对于常量val的reference wrapper

remove_if & find_if

find_if(ForwardIt first, ForwardIt last, UnaryPredicate p)找到一个满足p的迭代器first并返回
remove_if(ForwardIt first, ForwardIt last, UnaryPredicate p)返回全部满足条件p的迭代器的后一个位置,实则就是将满足条件的元素全部移动至容器前方,将不满足的全部移至后方,可搭配erase方法使用:

1
2
3
4
5
std::string str2 = "Text\n with\tsome \t  whitespaces\n\n";
str2.erase(std::remove_if(str2.begin(),
str2.end(),
[](unsigned char x){return std::isspace(x);}),
str2.end());

c++17中新引入的推导指引decution guide

推导指引就是可以省略显式指出类模板的模板实参,交给编译器去做推导
手动实现一个类模板的推导指引规则的语法为
explicit-specifier (optional) template-name ( parameter-declaration-clause ) -> simple-template-id
标准库的各大容器都实现了相应的推导指引规则
priority_queue对应的推导指引规则之一:

1
2
3
template< class Comp, class Container >
priority_queue( Comp, Container )
-> priority_queue<typename Container::value_type, Container, Comp>;

这样我们只需在实例化priority_queue时给出Comp和Container这两个实参就够了,具体例子:

1
2
3
priority_queue
// <ListNode*, vector<ListNode*>, function<bool(ListNode*, ListNode*)>>可省略
pq{[](auto& a, auto& b)->bool{return a->val > b->val;}, lists};

CPP实用编程中的一些小技巧和注意事项
http://example.com/2023/12/28/some-tricks-of-CPP/
作者
李凯华
发布于
2023年12月28日
许可协议