C++积累

STL

atomic

 

 

 

 

#include <atomic>

原子操作(atomic): 互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量。原子变量既不可复制亦不可移动。(1)它表示在多个线程访问同一个全局资源的时候,能够确保所有其他的线程都不在同一时间内访问相同的资源。也就是他确保了在同一时刻只有唯一的线程对这个资源进行访问。这有点类似互斥对象对共享资源的访问的保护,但是原子操作更加接近底层,因而效率更高。是线程安全的。

(2)原子数据类型不会发生数据竞争,能直接用在多线程中而不必我们用户对其进行添加互斥资源锁的类型。从实现上,大家可以理解为这些原子类型内部自己加了锁。

(3)C++11中所有的原子类都是不允许拷贝、不允许Move的,atomic_flag也不例外。

(4)C++11 对常见的原子操作进行了抽象,定义出统一的接口,并根据编译选项/环境产生平台相关的实现。新标准将原子操作定义为atomic模板类的成员函数,囊括了绝大多数典型的操作——读、写、比较、交换

contained type

atomic type

bool

atomic_bool

char

atomic_char

signed char

atomic_schar

unsigned char

atomic_uchar

short

atomic_short

unsigned short

atomic_ushort

int

atomic_int

unsigned int

atomic_uint

long

atomic_long

unsigned long

atomic_ulong

long long

atomic_llong

unsigned long long

atomic_ullong

wchar_t

atomic_wchar_t

char16_t

atomic_char16_t

char32_t

atomic_char32_t

intmax_t

atomic_intmax_t

uintmax_t

atomic_uintmax_t

int_leastN_t

atomic_int_leastN_t

uint_leastN_t

atomic_uint_leastN_t

int_fastN_t

atomic_int_fastN_t

uint_fastN_t

atomic_uint_fastN_t

intptr_t

atomic_intptr_t

uintptr_t

atomic_uintptr_t

size_t

atomic_size_t

ptrdiff_t

atomic_ptrdiff_t

(4)macro

macro

relative to types

ATOMIC_BOOL_LOCK_FREE

bool

ATOMIC_CHAR_LOCK_FREE

char

signed char

unsigned char

ATOMIC_SHORT_LOCK_FREE

short

unsigned short

ATOMIC_INT_LOCK_FREE

int

unsigned int

ATOMIC_LONG_LOCK_FREE

long

unsigned long

ATOMIC_LLONG_LOCK_FREE

long long

unsigned long long

ATOMIC_WCHAR_T_LOCK_FREE

wchar_t

ATOMIC_CHAR16_T_LOCK_FREE

char16_t

ATOMIC_CHAR32_T_LOCK_FREE

char32_t

ATOMIC_POINTER_LOCK_FREE

U*

(for any type U)

(5)memory_order:内存顺序

序号

意义

1

memory_order_relaxed

宽松模型,不对执行顺序做保证

2

memory_order_consume

当前线程中,满足happens-before原则。

当前线程中该原子的所有后续操作,必须在本条操作完成之后执行

3

memory_order_acquire

当前线程中,操作满足happens-before原则。

所有后续的操作必须在本操作完成后执行

4

memory_order_release

当前线程中,操作满足happens-before原则。

所有后续的操作必须在本操作完成后执行

5

memory_order_acq_rel

当前线程中,同时满足memory_order_acquire和memory_order_release

6

memory_order_seq_cst

最强约束。全部读写都按顺序执行

(6)Functions

(7)std::atomic的限制:trivially copyable(可平凡复制)一个类型如果是trivially copyable,则使用memcpy这种方式把它的数据从一个地方拷贝出来会得到相同的结果。

1.没有non-trivial 的拷贝构造函数

2.没有non-trivial的move构造函数

3.没有non-trivial的赋值操作符

4.没有non-trivial的move赋值操作符

5.有一个trivial的析构函数

std::atomic_flag:最简单的原子变量实例是对于bool类型的变量进行原子操作,提供了标志的管理,标志有三种状态:clear、set和未初始化状态。

接口介绍:

(1)ATOMIC_FLAG_INIT:用于给atomic_flag变量赋初值,如果定义后为赋值,则状态是不确定的。被这个赋值后的状态为false。

(2)test_and_set() 接口函数,调用后状态变为true,并返回改变状态前的状态值

(3)clear() : 接口函数,调用后状态变为false。

 

#include <thread>

#include <vector>

#include <iostream>

#include <atomic>

std::atomic_flag lock = ATOMIC_FLAG_INIT;

int gcnt = 0;

void f(int n)

{

    for (int cnt = 0; cnt < 100; ++cnt) {

        while (lock.test_and_set(std::memory_order_acquire))  // 获得锁

             ; // 自旋

        std::cout << "Output from thread " << n << '\n';

        gcnt++;

        lock.clear(std::memory_order_release);               // 释放锁

    }

}

int main()

{

    std::vector<std::thread> v;

    for (int n = 0; n < 10; ++n) {

        v.emplace_back(f, n);

    }

    for (auto& t : v) {

        t.join();

    }

}

自旋锁的解释:当某一个线程调用‘lock.test_and_set’时,即设置lock的状态为true,当另一个线程进入时,再次调用test_and_set时返回的状态为true,则一直在while循环中不断获取,即实现了等待,直到第一个线程调用clear将状态变为false。

std::atomic<T>:通过atomic模板类可以对更多的类型进行原子操作

(1)is_lock_free:通过这个接口判断是否需要加锁。如果是,则在多个线程访问该对象时不会导致线程阻塞(可能使用某种事务内存transactional memory方法实现lock-free的特性)。事实上该函数可以做为一个静态函数。所有指定相同类型T的atomic实例的is_lock_free函数都会返回相同值。

(2)store:赋值操作。operator=实际上内部调用了store,并返回d。

void store(T desr, memory_order m = memory_order_seq_cst) noexcept;
void store(T desr, memory_order m = memory_order_seq_cst) volatile noexcept;
T
operator=(T d) noexcept;
T
operator=(T d) volatile noexcept;

(3)load: 读取,加载并返回变量的值。operator T是load的简化版,内部调用的是load(memory_order_seq_cst)形式。

(4)exchange交换,赋值后返回变量赋值前的值。exchange也称为read-modify-write操作。

T exchange(T desr, memory_order m = memory_order_seq_cst) volatile noexcept;

T exchange(T desr, memory_order m = memory_order_seq_cst) noexcept;

(5)compare_exchange_weak这就是有名的CAS(Compare And Swap: 比较并交换)。操作是原子的,排它的。其它线程如果想要读取或修改该原子对象时,会等待先该操作完成。

(6)compare_exchange_strong:

进行compare时,与weak版一样,都是比较的物理内容。与weak版不同的是,strong版本不会返回伪false。即:原子对象所封装的值如果与expect在物理内容上相同,strong版本一定会返回true。其所付出的代价是:在某些需要循环检测的算法,或某些平台下,其性能较compare_exchange_weak要差。但对于某些不需要采用循环检测的算法而言, 通常采用compare_exchange_strong 更好。

std::atomic特化:

(1)fetch_add该函数将原子对象封装的值加上v,同时返回原子对象的旧值。其功能用伪代码表示为:

// T is integral

T fetch_add(T v, memory_order m = memory_order_seq_cst) volatile noexcept;

T fetch_add(T v, memory_order m = memory_order_seq_cst) noexcept;

// T is pointer

T fetch_add(ptrdiff_t v, memory_order m = memory_order_seq_cst) volatile noexcept;

T fetch_add(ptrdiff_t v, memory_order m = memory_order_seq_cst) noexcept;

(2)fetch_sub该函数将原子对象封装的值减去v,同时返回原子对象的旧值。其功能用伪代码表示为:

// T is integral

T fetch_sub(T v, memory_order m = memory_order_seq_cst) volatile noexcept;

T fetch_sub(T v, memory_order m = memory_order_seq_cst) noexcept;

// T is pointer

T fetch_sub(ptrdiff_t v, memory_order m = memory_order_seq_cst) volatile noexcept;

T fetch_sub(ptrdiff_t v, memory_order m = memory_order_seq_cst) noexcept;

(3)++, --, +=, -=不管是基于整数的特化,还是指针特化,atomic均支持这四种操作。其用法与未封装时一样

独属于数值型特化的原子操作 - 位操作:

(1)fetch_and,fetch_or,fetch_xor:

位操作,将contained按指定方式进行位操作,并返回contained的旧值。

integral fetch_and(integral v, memory_order m = memory_order_seq_cst) volatile noexcept;

integral fetch_and(integral v, memory_order m = memory_order_seq_cst) noexcept;

integral fetch_or(integral v, memory_order m = memory_order_seq_cst) volatile noexcept;

integral fetch_or(integral v, memory_order m = memory_order_seq_cst) noexcept;

integral fetch_xor(integral v, memory_order m = memory_order_seq_cst) volatile noexcept;

integral fetch_xor(integral v, memory_order m = memory_order_seq_cst) noexcept;

(2)perator &=operator |=operator ^=与相应的fetch_*操作不同的是,operator操作返回的是新值

T operator &=(T v) volatile noexcept {return fetch_and(v) & v;}

T operator &=(T v) noexcept {return fetch_and(v) & v;}

T operator |=(T v) volatile noexcept {return fetch_or(v) | v;}

T operator |=(T v) noexcept {return fetch_or(v) | v;}

T operator ^=(T v) volatile noexcept {return fetch_xor(v) ^ v;}

T operator ^=(T v) noexcept {return fetch_xor(v) ^ v;}

thread

#include <thread>

(1)

(2)tread函数:

get_id

获取线程 ID。

joinable

检查线程是否可被join。

join

Join 线程。

detach

Detach 线程

swap

native_handle

hardware_concurrency  [static]

(1)接口函数:

函数

详解

示例

std::async(std::launch::async,provider);

(1)作用:在于将其获取到的函数立即在一个新的线程内进行异步启动。也就是一个线程启动函数。

(2)std::aysnc()返回:会返回一个std::future object类型的返回值,在std::future object中,我们可以取得线程返回值或异常信息。此外,std::future object类型的特化与线程函数的返回值一致。

(3)指定std::aysnc()的发射策略(launch strategy):

std::async的策略主要有两个:

    1、std::launch::async  :  立即尝试启动异步调用,如果在此处无法进行调用时,会返回一个std::system_error

    2、std::launch::deferred : 延缓线程的启动,直到我们手动调用future::get()时,线程才会启动。

(1)示例1std::aysnc(func1) //无参数形式

(2)示例2:

std::aysnc(func1,arg1,arg2) //向函数func1传递arg1,arg2;

(3)示例3:

void func1(int A = 0);

int func2();

int main()

{

std::future<void> func1_res(std::async(print_A,10));

          //func1返回类型为void,future object的类型也为void

std::future<int> func2_res(std::async(print_B));

           //func2返回类型为int,future object的类型也为int

}

(4)示例4:

int main()

{

    //f1在这里就启动了,输出func1 start!

std::future<void> f1(std::async(std::launch::async, func1));

    //f2在这里由于发射策略的原因,并没有启动

std::future<void> f2(std::async(std::launch::deferred, func2));

Sleep(3000);

std::cout << "3 seconds later!" << std::endl;

    //三秒之后,由于调用future::get(),线程f2启动,输出func2 start!

f2.get();

return 0;

}

std::future

 

std::shared_future

 

 

 

 

std::thread

(1)作用:

(2)与async比较:

1、Class thread 没有发射策略,只要我们实例化Class thread的对象,系统就会尝试启动目标函数,如果无法启动目标函数,就会抛出std::system_error并携带差错resource_unavailable_try_again。

2、Class thread并不提供处理线程结果的接口

3、必须对线程的状态进行声明,等待其结束(join())或直接卸载(detach())

4、如果main()函数结束了,所有线程会被直接终止

示例1:

std::thread t1(Print, 1); //创建线程1

std::thread t2(Print, 2); //创建线程2

t1.join(); //等待线程1结束

t2.join(); //等待线程2结束

 

std::promise

 

 

packaged_task

(1)作用:实现了运行我们自由控制启动线程的启动时间,可以用于实现线程池

示例1:

std::packaged_task<void()> task(func1);  //这里创建thread task,但是不会立即启动线程

Sleep(3000); //sleep3秒,当然,这里可以改成任何你需要的操作

task(); //3秒后启动线程

(2)互斥量与锁(Mutex & Lock):

std::mutex m_lock;

lock()

 

 

try_lock()

 

 

unlock()

 

 

try_lock_for()

 

 

try_lock_until()

 

 

lock_guard

(1)作用:

(2)使用:

std::lock_guard<T> lg(m_lock) //建立并锁定

std::lock_guard<T> lg(m_lock, adopt_lock) //为已锁定的m_lock建立lock_guard

(1)示例1:

std::mutex m_lock;

std::lock_guard<std::mutex> lg(m_lock);//上锁,超出作用于自动解锁

unique_lock

(1)作用:相对于Class lock_guard 来说,Class unique_lock 的特殊之处在于,可以让我们指定“何时”以及“如何”锁定和结果Mutex,此外,在Class unique_lock中,我们甚至可以用owns_lock()或bool()来查询目前Mutex是否会被锁住。

 

(3)条件变量(Condition Variable

 

 

(4)

()

enable_if_t

 

 

std::enable_if_t<!std::is_same<std::remove_cv_t<T>, A>::value> * = nullptr>

 

 

 

 

enable_if

 

 

is_same

 

 

remove_cv_t

 

 

std::ref

 

 

C++ 关键词

class

用法

class Foo;  // 类的前置声明

class Bar { // 类的定义

  public:

    Bar(int i) : m_i(i) {}

  private:

    int m_i;

};

template <class T> // 模板实参

void qux() {

    T t;

}

int main()

{

    Bar Bar(1);

    class Bar Bar2(2); // 详述的类型

}

this

语法

this

关键词 this 是一个纯右值表达式,其值是隐式对象形参(在其上调用非静态成员函数的对象)的地址。它能出现于下列语境:

1) 在任何非静态成员函数体内,包括成员初始化器列表

2) 在非静态成员函数的声明中,(可选的)cv 限定符序列之后的任何位置,包括动态异常说明(弃用)noexcept 说明(C++11)以及尾随返回类型(C++11 起)

3) 默认成员初始化器 (C++11 )

 

virtual

用法

virtual 说明符指定非静态成员函数函数并支持动态调用派发。它只能在非静态成员函数的首个声明(即当它于类定义中声明时)的 声明说明符序列 中出现。

于每个指定为 virtual 的不同基类,最终派生对象中仅含有该类型的一个基类子对象,即使该类在继承层级中出现多次也是如此(只要它每次都以 virtual 继承)。

 

explicit

用法

explicit

(1)

explicit ( 表达式 )

(2)

(C++20 起)

1) 指定构造函数或转换函数 (C++11 )推导指引 (C++17 )为显式,即它不能用于隐式转换复制初始化

2) explicit 说明符可以与常量表达式一同使用。当且仅当该常量表达式求值为 true 时函数为显式。

(C++20 起)

explicit 说明符只可出现于在类定义之内的构造函数或转换函数 (C++11 ) 声明说明符序列 中。

 

struct A
{
    A(int) { }      // 转换构造函数
    A(int, int) { } // 转换构造函数 (C++11)
    operator bool() const { return true; }
};
 
struct B
{
    explicit B(int) { }
    explicit B(int, int) { }
    explicit operator bool() const { return true; }
};
 
int main()
{
    A a1 = 1;      // OK:复制初始化选择 A::A(int)
    A a2(2);       // OK:直接初始化选择 A::A(int)
    A a3 {4, 5};   // OK:直接列表初始化选择 A::A(int, int)
    A a4 = {4, 5}; // OK:复制列表初始化选择 A::A(int, int)
    A a5 = (A)1;   // OK:显式转型进行 static_cast
    if (a1) ;      // OK:A::operator bool()
    bool na1 = a1; // OK:复制初始化选择 A::operator bool()
    bool na2 = static_cast<bool>(a1); // OK:static_cast 进行直接初始化
 
//  B b1 = 1;      // 错误:复制初始化不考虑 B::B(int)
    B b2(2);       // OK:直接初始化选择 B::B(int)
    B b3 {4, 5};   // OK:直接列表初始化选择 B::B(int, int)
//  B b4 = {4, 5}; // 错误:复制列表初始化不考虑 B::B(int,int)
    B b5 = (B)1;   // OK:显式转型进行 static_cast
    if (b2) ;      // OK:B::operator bool()
//  bool nb1 = b2; // 错误:复制初始化不考虑 B::operator bool()
    bool nb2 = static_cast<bool>(b2); // OK:static_cast 进行直接初始化
}

friend

说明

友元声明出现于类体内,并向一个函数或另一个类授予对包含友元声明的类的私有及受保护成员的访问权。

语法

friend 函数声明

(1)

friend 函数定义

(2)

friend 详述类说明符 ;

(3)

friend 简单类型说明符 ;

friend typename-说明符 ;

(4)

(C++11 起)

inline

说明:

inline 关键词的本意是作为给优化器的指示器,以指示优先采用函数的内联替换而非进行函数调用,即并不执行将控制转移到函数体内的函数调用 CPU 指令,而是代之以执行函数体的一份副本而无需生成调用。这会避免函数调用的开销(传递实参及返回结果),但它可能导致更大的可执行文件,因为函数体必须被复制多次。

因为关键词 inline 的含义是非强制的,编译器拥有对任何未标记为 inline 的函数使用内联替换的自由,和对任何标记为 inline 的函数生成函数调用的自由。这些优化选择不改变上述关于多个定义和共享静态变量的规则。

限制:

  1. inline 说明符不能用于块作用域内(函数内部)的函数或变量 (C++17 起)声明
  2. inline 说明符不能重声明在翻译单元中已定义为非内联的函数或变量 (C++17 起)。

用法

声明有 constexpr 的函数是隐式的内联函数。

弃置的函数是隐式的内联函数:其(弃置)定义可出现在多于一个翻译单元中。

(C++11 起)

完全在 class/struct/union 的定义之内定义的函数,无论它是成员函数还是非成员 friend 函数,均为隐式的内联函数。

 

inline 说明符,在用于具有静态存储期的变量(静态类成员或命名空间作用域变量)的 声明说明符序列 时,将变量声明为内联变量

声明为 constexpr 的静态成员变量(但不是命名空间作用域变量)是隐式的内联变量。

inline namespace 命名空间名 { 声明序列 }  / namespace 命名空间名::inline(C++20 起)(可选) 名字 { 声明序列 }        

见:“C++声明 命名空间"

 

template

用法

见模板篇

export

用法

用于标记模板定义被导出,这允许在其他翻译单元中声明但不定义同一模板。(1)

(C++11 前)

不使用并保留该关键词。

(C++11 起)

(C++20 前)

标记一个声明、一组声明或另一模块为当前模块所导出。

(C++20 起)

export 是可选的修饰符,模板被导出(用于声明类模板时,它也声明其所有成员被导出)。对被导出模板进行实例化的文件不需要包含其定义:声明即已充分。

export module name

  • 导出模块
  • export template < 形参列表 > 类声明

(1)

export template <typename T>

class MyClass

{

    public:

        void memfun1();     // 被导出的函数

        void memfun2(){ ... } // 隐式内联不能被导出

        ...

        void memfun3();      // 显式内联不能被导出

        ...

};

template <typename T>

inline void MyClass<T>::memfun3()   // 使用inline关键字,显式内联

{

    ...

}

(2)

// helloworld.cpp
export module helloworld;  // 模块声明
import <iostream>;         // import声明
 
export void hello() {      // export声明
    std::cout << "Hello world!\n";
}

// main.cpp
import helloworld;  // import声明
 
int main() {
    hello();
}

extern

用法

extern 字符串字面量 { 声明序列(可选) }

(1)

extern 字符串字面量 声明

(2)

1) 将语言说明 字符串字面量 应用到声明于 声明序列 中的所有函数类型,具有外部链接的函数名,和具有外部链接的变量。

2) 将语言说明 字符串字面量 应用到单一声明或定义。

字符串字面量

-

所要求的语言链接的名字

声明序列

-

声明的序列,可以包含嵌套的链接说明

声明

-

一个声明

extern template class|struct 模板名 < 实参列表 > ;  (C++11 起)

  • 显示模板实例化声明
  • "C++",默认的语言链接。
  • "C",使得以 C 程序语言编写的函数进行链接,以及在 C++ 程序中定义能从 C 模块调用的函数成为可能。
  • 外部链接声明
  • 提供以不同程序语言编写的模块间的链接。

(1)

(2)

extern "C" {
    int open(const char *pathname, int flags); // C 函数声明
}
 
int main()
{
    int fd = open("test.txt", 0); // 从 C++ 程序调用 C 函数
}
 
// 此 C++ 函数能从 C 代码调用
extern "C" void handler(int) {
    std::cout << "Callback invoked\n"; // 它能使用 C++
}

concept(C++20 )

约束与概念:

(1) 类模板,函数模板,以及非模板函数(常为类模板的成员),可以与约束(constraint)关联,它指定对模板实参的一些要求,这些要求可被用于选择最恰当的函数重载和模板特化。

(2) 这种要求的具名集合被称为概念(concept)。每个概念都是谓词,于编译时求值,并成为以之作为一项约束的模板接口的一部分:

 

const

说明:

用法

 

static

用法

 

volatile

用法

 

static_assert

头文件:

#include <type_traits>

语法

static_assert ( 布尔常量表达式 , 消息 )

(C++11 起)

static_assert ( 布尔常量表达式 )

(C++17 起)

解释

static_assert 声明可以出现在命名空间和块作用域中(作为块声明),也可以在类体中(作为成员声明)。

 布尔常量表达式 返回 true,则此声明无效果。否则发布编译时错误,而若存在 消息,则诊断消息中包含其文本。

注解

因为 消息 必须是字符串字面量,所以它不能容纳动态信息,乃至自身并非字符串字面量的常量表达式。特别是它不能容纳模板类型实参名字

#include <type_traits>

template <class T>

struct data_structure

{

    static_assert(std::is_default_constructible<T>::value,

                  "Data Structure requires default-constructible elements");

};

int main()

{

    data_structure<int> ds_ok;

}

sizeof

用法

语法

sizeof( 类型 )

(1)

sizeof 表达式

(2)

两个版本都是 std::size_t 类型的常量表达式。

查询形参包中的元素数量。

语法

sizeof...( 形参包 )

(C++11 起)

返回 std::size_t 类型的常量。

#include <iostream>
#include <array>
#include <type_traits>
 
template<typename... Ts>
constexpr auto make_array(Ts&&... ts)
    -> std::array<std::common_type_t<Ts...>,sizeof...(ts)>
{
    return { std::forward<Ts>(ts)... };
}
 
int main()
{
    auto b = make_array(1, 2, 3);
    std::cout << b.size() << '\n';
    for (auto i : b)
        std::cout << i << ' ';
}

mutable

许在即便包含它的对象被声明为 const 时仍可修改声明为 mutable 的类成员。

  • 可出现于非引用非 const 类型的非静态数据成员的声明中:
  • 从按复制捕获的形参中移除的 const 限定性的 lambda 声明符 (C++11 )

 

consteval(c++20)

说明:

  • consteval - 指定函数是立即函数(immediate function),即每次调用该函数必须产生编译时常量。

 

 

consteval int sqr(int n) {

  return n*n;

}

constexpr int r = sqr(100);  // OK

int x = 100;

int r2 = sqr(x);  // 错误:调用不产生常量

constexpr(c++11)

说明:

constexpr 说明符声明 可以 在编译时求 得 函数 或变量的 值。 然后这些 变量和函数(若给定了合适的函数实参)即可用于仅允许编译时常量表达式之处。

constexpr 变量必须满足下列要求:

constexpr 函数必须满足下列要求:

  • 它必须非

(C++20 前)

(C++20 起)

(C++20 前)

(C++14 前)

(C++20 前)

(=default; 或 =delete; 的函数体不含任何上述内容。)

  • 非字面类型的变量定义
  • 静态或线程存储期变量的定义
  • 函数体必须含:
    • 拥有除 case 和 default 之外的标号的语句

(C++14 起)

函数体非 =delete; 的 constexpr 构造函数必须满足下列额外要求:

  • 对于 class 或 struct 的构造函数,每个子对象和每个非变体非 static 数据成员必须被初始化。若类是联合体式的类,对于其每个非空匿名联合体成员,必须恰好有一个变体成员被初始化
  • 对于非空 union 的构造函数,恰好有一个非静态数据成员被初始化

(C++20 前)

析构函数不能为 constexpr ,但能在常量表达式中调用平凡析构函数

(C++20 前)

函数体非 =delete; 的 constexpr 析构函数必须满足下列额外要求:

  • 每个用于销毁非静态数据成员与基类的析构函数必须为 constexpr 析构函数。

(C++20 起)

对于 constexpr 函数模板和类模板的 constexpr 函数成员,必须至少有一个特化满足上述要求。其他特化仍被认为是 constexpr,尽管常量表达式中不能出现这种函数的调用。

带初始化器的 if 语句

(1)

constexpr int factorial(int n)
{
    return n <= 1? 1 : (n * factorial(n - 1));
}

(2)

template <typename T>
auto get_value(T t) {
    if constexpr (std::is_pointer_v<T>)
        return *t; // 对 T = int* 推导返回类型为 int
    else
        return t;  // 对 T = int 推导返回类型为 int
}

constinit(c++20)

说明: 说明符声明拥有静态或线程存储期的变量。

constinit 不能和 constexpr 或 consteval 一同使用。声明的变量为引用时, constinit 等价于 constexpr 。声明的变量为对象时, constexpr 强制对象必须拥有静态初始化和常量析构,并使对象有 const 限定,然而 constinit 不强制常量析构和 const 限定。

(1)初始化声明

const char *g() { return "dynamic initialization"; }
constexpr const char *f(bool p) { return p ? "constant initializer" : g(); }
 
constinit const char *c = f(true); // OK
// constinit const char *d = f(false); // 错误

(2)用于非初始化声明,以告知编译器 thread_local 变量已被初始化。

extern thread_local constinit int x;
int f() { return x; } // 无需检查防卫变量

const_cast

说明:

在有不同 cv 限定的类型间转换。

语法

const_cast < 新类型 > ( 表达式 )

返回 新类型 类型的值。

解释

(1) const_cast只能改变运算对象的底层const,也就是说:

•常量指针转化为非常量的指针,并且仍然指向原来的对象

•常量引用转化为非常量的引用,并且仍然指向原来的对象

(2) 不能用const_cast改变表达式的类型

const char* cp;

const_cast<string>(cp); //错误:const_cast不能改变表达式类型,只能改变常量属性

(3)使用const_cast把一个原本const的变量转换为非const是一个非定义行为未定义行为指的是这个语句在标准C++中没有的规定,由编译器来决定如何处理

 

(1)

const int value=12;

int new_value=const_cast<int>(value); //错误:const_cast只能改变运算对象的底层const,而对顶层const无能为力

const int* value_ptr=&value;

int *ptr=const_cast<int*>(value_ptr);//正确:将常量指针转化为非常量指针,并仍然指向原来的对象

const int& value_re=value;

int& re=const_cast<int&>(value_re);//正确:将常量引用转化为非常量引用,并仍然指向原来的对象

(3)

struct type {
    int i;
 
    type(): i(3) {}
 
    void f(int v) const {
        // this->i = v;                 // 编译错误:this 是指向 const 的指针
        const_cast<type*>(this)->i = v; // 只要该对象不是 const 就 OK
    }
};

dynammic_cast

说明:

沿继承层级向上、向下及侧向,安全地转换到其他类的指针和引用。它支持运行时识别指针或引用

语法

dynamic_cast < 新类型 > ( 表达式 )

新类型

-

指向完整类类型的指针、到完整类类型的引用,或指向(可选的 cv 限定)void 的指针

表达式

-

 新类型 为引用,则为完整类类型的左值 (C++11 )泛左值 (C++11 )表达式,若 新类型 为指针,则为指向完整类类型的指针纯右值。

若转型成功,则 dynamic_cast 返回 新类型 类型的值。若转型失败且 新类型 是指针类型,则它返回该类型的空指针。若转型失败且 新类型 是引用类型,则它抛出与类型 std::bad_cast 的处理块匹配的异常。

解释

唯有下列转换能用 dynamic_cast 进行,但若这种转换会转换走常量性易变性则亦不允许。

1)  表达式 的类型恰是 新类型  新类型 的较少 cv 限定版本,则结果是 表达式 具有 新类型 类型的值。(换言之,dynamic_cast 可用以添加常量性。隐式转换和 static_cast 亦能进行此转换。)

2)  表达式 的值是空指针值,则结果是 新类型 类型的空指针值。

3)  新类型 是到 Base 的指针或引用,且 表达式 的类型是到 Derived 的指针或引用,其中 Base 是 Derived 的唯一可访问基类,则结果是到 表达式 所标识的对象中 Base 类子对象的指针或引用。(注意:隐式转换和 static_cast 亦能进行此转换。)

4)  表达式 是指向多态类型的指针,且 新类型 是到 void 的指针,则结果是指向 表达式 所指向或引用的最终派生对象的指针。

5)  表达式 是到多态类型 Base 的指针或引用,且 新类型 是到 Derived 类型的指针或引用,则进行运行时检查:

a) 检验 表达式 所指向/标识的最终派生对象。若在该对象中 表达式 指向/指代 Derived 的公开基类,且只有一个 Derived 类型对象从 表达式 所指向/标识的子对象派生,则转型结果指向/指代该 Derived 对象。(此之谓“向下转型(downcast)”。)

b) 否则,若 表达式 指向/指代最终派生对象的公开基类,而同时最终派生对象拥有 Derived 类型的无歧义公开基类,则转型结果指向/指代该 Derived(此之谓“侧向转型(sidecast)”。)

c) 否则,运行时检查失败。若 dynamic_cast 用于指针,则返回 新类型 类型的空指针值。若它用于引用,则抛出 std::bad_cast 异常。

6) 当在构造函数或析构函数中(直接或间接)使用 dynamic_cast,且 表达式 指代正在构造/销毁的对象时,该对象被认为是最终派生对象。若 新类型 不是到构造函数/析构函数自身的类或其基类之一的指针或引用,则行为未定义。

与其他转型表达式相似:

注解

  • 亦可用 static_cast 进行向下转型,它避免运行时检查的开销,但只有在程序(通过某些其他逻辑)能够保证 表达式 所指向的对象肯定是 Derived 时才是安全的。
  •  新类型 是左值引用类型(表达式 必为左值),则其结果为左值
  •  新类型 是右值引用类型(表达式 为完整类类型,可以是左值或右值 (C++17 )必为泛左值(纯右值被实质化 (C++17 )),则其结果为亡值
  •  新类型 是指针类型,则其结果为纯右值

struct V {

    virtual void f() {};  // 必须为多态以使用运行时检查的 dynamic_cast

};

struct A : virtual V {};

struct B : virtual V {

  B(V* v, A* a) {

    // 构造中转型(见后述 D 的构造函数中的调用)

    dynamic_cast<B*>(v); // 良好定义:v 有类型 V*,B 的 V 基类,产生 B*

    dynamic_cast<B*>(a); // 未定义行为:a 有类型 A*,A 非 B 的基类

  }

};

struct D : A, B {

    D() : B((A*)this, this) { }

};

struct Base {

    virtual ~Base() {}

};

struct Derived: Base {

    virtual void name() {}

};

int main()

{

    D d; // 最终派生对象

    A& a = d; // 向上转型,可以用 dynamic_cast,但不必须

    D& new_d = dynamic_cast<D&>(a); // 向下转型

    B& new_b = dynamic_cast<B&>(a); // 侧向转型

    Base* b1 = new Base;

    if(Derived* d = dynamic_cast<Derived*>(b1))

    {

        std::cout << "downcast from b1 to d successful\n";

        d->name(); // 可以安全调用

    }

    Base* b2 = new Derived;

    if(Derived* d = dynamic_cast<Derived*>(b2))

    {

        std::cout << "downcast from b2 to d successful\n";

        d->name(); // 可以安全调用

    }

    delete b1;

    delete b2;

}

reinterpret_cast

说明:

通过重新解释底层位模式在类型间转换。

reinterpret_cast运算符是用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位。但若转换会转型走常量性或易变性则亦不允许。

语法

reinterpret_cast < 新类型 > ( 表达式 )

解释

与 static_cast 不同,但与 const_cast 类似,reinterpret_cast 表达式不会编译成任何 CPU 指令(除非在整数和指针间转换,或在指针表示依赖其类型的不明架构上)。它纯粹是一个编译时指令,指示编译器将 表达式 视为如同具有 新类型 类型一样处理。

reinterpret_cast用在任意指针(或引用)类型之间的转换;以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。

  • 从指针类型到一个足够大的整数类型
  • 从整数类型或者枚举类型到指针类型
  • 从一个指向函数的指针到另一个不同类型的指向函数的指针
  • 从一个指向对象的指针到另一个不同类型的指向对象的指针
  • 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
  • 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针

int f() { return 42; }

int main()

{

    int i = 7;

    // 指针到整数并转回

    std::uintptr_t v1 = reinterpret_cast<std::uintptr_t>(&i); // static_cast 为错误

    std::cout << "The value of &i is 0x" << std::hex << v1 << '\n';

    int* p1 = reinterpret_cast<int*>(v1);

    assert(p1 == &i);

    // 到另一函数指针并转回

    void(*fp1)() = reinterpret_cast<void(*)()>(f);

    // fp1(); 未定义行为

    int(*fp2)() = reinterpret_cast<int(*)()>(fp1);

    std::cout << std::dec << fp2() << '\n'; // 安全

    // 通过指针的类型别名化

    char* p2 = reinterpret_cast<char*>(&i);

    if(p2[0] == '\x7')

        std::cout << "This system is little-endian\n";

    else

        std::cout << "This system is big-endian\n";

    // 通过引用的类型别名化

    reinterpret_cast<unsigned int&>(i) = 42;

    std::cout << i << '\n';

    [[maybe_unused]] const int &const_iref = i;

    // int &iref = reinterpret_cast<int&>(const_iref); // 编译错误——不能去除 const

    // 必须用 const_cast 代替:int &iref = const_cast<int&>(const_iref);

}

static_cast

说明

用隐式和用户定义转换的组合在类型间转换。static_cast相当于传统的C语言里的强制转换(相关类型转换)

static_cast不能转换掉expression的const、volatile、或者__unaligned属性

语法

static_cast < 新类型 > ( 表达式 )

解释

唯有下列转换能用 static_cast 执行,但若转换会转型走常量性易变性则亦不允许。

①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。

进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;

进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。

②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。

③把空指针转换成目标类型的空指针。

④把任何类型的表达式转换成void类型。

基本类型:

char a = 'a';

int b = static_cast<char>(a);//正确,将char型数据转换成int型数据

double *c = new double;

void *d = static_cast<void*>(c);//正确,将double指针转换成void指针

int e = 10;

const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据

const int g = 20;

int *h = static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性

interpret_casestatic_cast对比

简而言之,static_cast<> 将尝试转换,举例来说,如float-到-integer,而reinterpret_cast<>简单改变编译器的意图重新考虑那个对象作为另一类型。

float f = 12.3;

float* pf = &f;

// static cast<>

// 成功编译, n = 12

int n = static_cast<int>(f);

// 错误,指向的类型是无关的(译注:即指针变量pf是float类型,现在要被转换为int类型)

//int* pn = static_cast<int*>(pf);

//成功编译

void* pv = static_cast<void*>(pf);

//成功编译, 但是 *pn2是无意义的内存(rubbish)

int* pn2 = static_cast<int*>(pv);// reinterpret_cast<>

//错误,编译器知道你应该调用static_cast<>

//int i = reinterpret_cast<int>(f);

//成功编译, 但是 *pn 实际上是无意义的内存,和 *pn2一样

int* pi = reinterpret_cast<int*>(pf);

noexcept

头文件:

#include <utility>

用法

noexcept 运算符进行编译时检查,若表达式声明为不抛出任何异常则返回 true

它可用于函数模板的 noexcept 说明符中,以声明函数将对某些类型抛出异常,但不对其他类型抛出。

语法

noexcept( 表达式 )

返回 bool 类型的纯右值

noexcept 运算符不对 表达式 求值。

 表达式 潜在异常集合为空 (C++17 )表达式 不抛出 (C++17 ) 则结果为 true,否则结果为 false

指定函数是否抛出异常。

语法

noexcept

(1)

noexcept(表达式)

(2)

throw()

(3)

(弃用)(C++20 中移除)

1) 与 noexcept ( true ) 相同

2)  表达式 求值为 true,则声明函数为不抛出任何异常。

void may_throw();

void no_throw() noexcept;

auto lmay_throw = []{};

auto lno_throw = []() noexcept {};

class T{

public:

  ~T(){} // 析构函数妨碍了移动构造函数

         // 复制构造函数为 noexcept

};

>>>

noexcept(may_throw())  //false

noexcept(no_throw())        //true

noexcept(lmay_throw())  //false

noexcept(lno_throw())        //true

noexcept(std::declval<T>().~T())  //true

noexcept(T(std::declval<T>()))        //true

noexcept(T(t))        //true

// foo 是否声明为 noexcept 取决于 T() 是否抛任何异常
template <class T>
  void foo() noexcept(noexcept(T())) {}
 
void bar() noexcept(true) {}
void baz() noexcept { throw 42; }  // noexcept 等同于 noexcept(true)
 
int main()
{
    foo<int>();  // noexcept(noexcept(int())) => noexcept(true),故这是可以的
 
    bar();  // 可以
    baz();  // 能编译,但在运行时会调用 std::terminate
}

alignas(c++11)

头文件:<stdalign.h> 与 <cstdalign>

说明:指定类型或对象的对齐要求。说明符可应用于变量或非位域类数据成员的

声明,或可应用于 class/struct/union 或枚举的定义。它不能应用于函数形参

或 catch 子句的异常形参。同一声明上,弱于其他 alignas 的有效的非零对齐被忽略。始终忽略 alignas(0)。

alignas( 表达式 ):必须是求值为零或合法的对齐或扩展对齐的整型常量表达式。

alignas( 类型标识 ):等价于 alignas(alignof(类型))

alignas( ... ):等价于对同一说明应用多个 alignas 说明符,逐个对应于形参包的各个成员,

形参包可以是类型或非类型形参包。

// sse_t 类型的每个对象将对齐到 16 字节边界
struct alignas(16) sse_t
{
  float sse_data[4];
};
// 数组 "cacheline" 将对齐到 128字节边界
alignas(128) char cacheline[128];

alignof 运算符(c++11)

头文件

说明:查询类型的对齐要求。该类型可以为完整对象类型、元素类型完整的数组类型或者到这些类型之一

的引用类型。若类型为引用类型,则运算符返回被引用类型的对齐;若类型为数组类型,

则返回元素类型的对齐要求。

alignof( 类型标识 ):返回std::size_t类型的值

#include <iostream>

struct Foo {

    int   i;

    float f;

    char  c;

};

struct Empty {};

struct alignas(64) Empty64 {};

int main()

{

    std::cout << "Alignment of"  "\n"   "- char             : " << alignof(char)    << "\n"

        "- pointer          : " << alignof(int*)    << "\n"

        "- class Foo        : " << alignof(Foo)     << "\n"

        "- empty class      : " << alignof(Empty)   << "\n"

        "- alignas(64) Empty: " << alignof(Empty64) << "\n";

}

std::max_align_t(c++11)

头文件:<cstddef>

说明:std::max_align_t 通常是最大标量类型的同意词,在大多数平台上是 long

double ,而其对齐要求是 8 或 16 。

 

#include <iostream>
#include <cstddef>
int main()
{
    std::cout << alignof(std::max_align_t) << '\n';
}

>> 16

and

and_eq

bitand

bitor

compl

not

not_eq

or

or_eq

xor

xor_eq

<%

%>

<:

:>

%:

%:%:

&&        

&=        

&        

|        

~        

!        

!=        

||        

|=        

^        

^=        

{        

}        

[        

]        

#        

##        

if(n > 0 and n < 5)

value  and_eq data;

asm

说明:给予在 C++ 程序中嵌入汇编语言源代码的能力。

asm ( 字符串字面量 )  字符串字面量 通常是以汇编语言编写的短程序,每当执行这条声明时对其执行。

atomic_cancel (TM TS)

atomic_commit (TM TS)

atomic_noexcept (TM TS)

说明:

事务性内存(transactional memory是在事务中结合语句组的并发同步机制,事务具有:

原子性(atomic)(要么语句全部发生,要么全部不发生)

隔离性(isolated)(事务中的语句不会观察到另一事务写入一半,即使它们并行执行)

同步块synchronized 复合语句

(1)如同在一个全局锁下执行复合语句:程序中的所有最外层同步块都以一个单独的全序执行。

在该顺序中,每个同步块的结尾同步于(synchronize with)下个同步块的开始。内嵌于其

他同步块的同步块没有特殊语义。同步块不是事务(不同于后面的原子块),并可以调用事务不安全的函数。

(2)以任何方式(抵达结尾,执行 goto、break、continue 或 return,或抛出异常)离开同步块都会退出该块,

而若所退出的块是外层块,则这在单一全序中同步于下个同步块。

不允许用 goto 或 switch 进入同步块。

原子块:

atomic_noexcept 复合语句

atomic_cancel 复合语句

atomic_commit 复合语句

1) 若抛出异常,则调用 std::abort

2) 若抛出异常,则调用 std::abort,除非该异常是用于事务取消的异常之一(见后述),这种情况下事务被取消(cancel):程序中所有由该原子块的各操作的副作用所修改的内存位置的值,被还原到该原子块的执行开始时它们曾拥有的值,而异常照常持续栈回溯。

3) 若抛出异常,则正常地提交事务。

用于 atomic_cancel 块中的事务取消的异常有 std::bad_alloc、std::bad_array_new_length、std::bad_cast、std::bad_typeid、std::bad_exception、std::exception 和所有从它派生的标准库异常,以及特殊异常类型 std::tx_exception<T>。

不允许原子块中的 复合语句 执行任何非 transaction_safe 的表达式或语句,或调用非 transaction_safe 的函数(这是编译时错误)。

(4)以除异常之外的任何方式(抵达结尾、goto、break、continue、return)离开原子块时,将提交事务。若用 std::longjmp 退出原子块则行为未定义。

事务安全的函数:

可在函数声明中用关键词 transaction_safe 将其显式声明为事务安全。

(1)

#include <iostream>

#include <vector>

#include <thread>

int f()

{

    static int i = 0;

    synchronized { // 开始同步块

        std::cout << i << " -> ";

        ++i;       // 每次调用 f() 都获得 i 的唯一值

        std::cout << i << '\n';

        return i; // 结束同步块

    }

}

int main()

{

    std::vector<std::thread> v(10);

    for(auto& t: v)

        t = std::thread([]{ for(int n = 0; n < 10; ++n) f(); });

    for(auto& t: v)

        t.join();

}

>>>

0 -> 1

1 -> 2

2 -> 3

...

99 -> 100

(2)

// 每次调用 f() 都取得 i 的唯一值,即使以并行进行
int f()
{
   static int i = 0;
   atomic_noexcept { // 开始事务
//   printf("before %d\n", i); // 错误:不能调用非事务安全的函数
      ++i;
      return i; // 提交事务
   }
}

auto

(1)类型推导C++11 起:

对于变量,指定要从其初始化器自动推导出其类型。

对于函数,指定要从其 return 语句推导出其返回类型。(C++14 起)

对于非类型模板形参,指定要从实参推导出其类型。(C++17 起)

(2):带尾随返回类型的函数声明:

尾随返回类型仅在最外层函数声明符中允许使用。此情况下的 声明说明符序列 必须包含关键词 auto

(3)结构化绑定声明

见:结构化绑定声明(c++17)

(1)

auto (1) (C++11 起)

decltype(auto) (2) (C++14 起)

类型制约 auto (3) (C++20 起)

类型制约 decltype(auto) (4) (C++20 起)

(2)

auto 说明符亦可用于后随尾随返回类型的函数声明符,该情况下返回类型为其尾随返回类型(它也可以是占位符类型):

auto (*p)() -> int; // 声明指向返回 int 的函数的指针

尾随返回类型,当返回类型取决于实参名时,例如 template <class T, class U> auto add(T t, U u) -> decltype(t + u);,或当返回类型复杂时,例如在 auto fpif(int)->int(*)(int) 中,尾随返回类型很有用

若函数声明的 声明说明符序列 包含关键词 auto,则尾随返回类型可以省略,而编译器将从 return 语句中所用的表达式的类型推导出它。若返回类型使用的不是 decltype(auto),则推导遵循模板实参推导的规则进行。

(1)

template<class T, class U>
auto add(T t, U u) { return t + u; } // 返回类型是 operator+(T, U) 的类型

(2)

// 在其所调用的函数返回引用的情况下

// 函数调用的完美转发必须用 decltype(auto)

template<class F, class... Args>

decltype(auto) PerfectForward(F fun, Args&&... args)

{

    return fun(std::forward<Args>(args)...);

}

(3)

template<auto n> // C++17 auto 形参声明
auto f() -> std::pair<decltype(n), decltype(n)> // auto 不能从花括号初始化器列表推导
{
    return {n, n};
}

bool/break/switch/case/continue/switch/do/if/enum/false/for/goto/while/void/return/requires

常规操作

char/char8_t(c++20)/char16_t(c++20)/char32_t(c++20)/double/float/int/long/short/

wchar_t/unsigned/signed/register

 

default

用法

类名() = default ;

delete

::(可选)    delete    表达式

::(可选)    delete [] 表达式

如果取代函数体而使用特殊语法 = delete ;,则该函数被定义为弃置的(deleted)。任何弃置函数的使用都是非良构的(程序无法编译)。

类名() = delete ;

new

用法

语法

::(可选) new (布置参数)(可选) ( 类型 ) 初始化器(可选)

(1)

::(可选) new (布置参数)(可选) 类型 初始化器(可选)

(2)

1) 尝试创建类型标识 类型 所指代的类型的一个对象,它可以是数组类型,可以包含占位符类型说明符 (C++11 起),或包含将由类模板实参推导推出的类模板名 (C++17 起)。

2) 同上,但 类型 不能包含括号:

operator new, operator new[]

  • 分配函数:作为运算符式的函数名
  • new 表达式
    1. 创建并初始化拥有动态存储期的对象,这些对象的生存期不受它们创建时所在的作用域限制。
    1. new 表达式尝试申请存储空间,并在已申请的存储空间上,尝试构造并初始化为一个无名对象,或无名对象的数组。new表达式返回一个指向所构造的对象或者对象数组的纯右值指针。

operator

说明:

为用户定义类型的操作数定制 C++ 运算符。

语法

重载的运算符是具有特殊的函数名的函数

operator op

(1)

operator 类型

(2)

operator new

operator new []

(3)

operator delete

operator delete []

(4)

operator "" 后缀标识符

(5)

(C++11 起)

operator co_await

(6)

(C++20 起)

op

-

下列 38 (C++20 )39 (C++20 ) 个运算符之一: + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= <=> (C++20 起) && || ++ -- , ->* -> ( ) [ ]

try-catch

说明:将一或多个异常处理块(catch 子句)与复合语句关联。

语法

try 复合语句 处理块序列   其中 处理块序列 是一或多个 处理块 的序列,它有下列语法:

catch ( attr(可选) 类型说明符序列 声明符 ) 复合语句

(1)

catch ( attr(可选) 类型说明符序列 抽象声明符(可选) ) 复合语句

(2)

catch ( ... ) 复合语句

(3)

throw

用法

异常

thread_local

用法

  • 这类对象的存储在线程开始时分配,并在线程结束时解分配。每个线程拥有其自身的对象实例。只有声明为 thread_local 的对象拥有此存储期。
  • 关键词仅允许搭配声明于命名空间作用域的对象、声明于块作用域的对象及静态数据成员。它指示对象具有线程存储期。它能与 static 或 extern 结合,以分别指定内部或外部连接(但静态数据成员始终拥有外部链接),但额外的 static 不影响存储期。(C++11 起)

thread_local unsigned int rage = 1;

协程:

协程是能暂停执行以在之后恢复的函数。协程是无栈的:它们通过返回到调用方暂停执行,并且从栈分离存储恢复所要求的数据。这允许编写异步执行的顺序代码(例如不使用显式的回调来处理非阻塞 I/O),还支持对惰性计算的无限序列上的算法及其他用途。

若函数的定义做下列任何内容之一,则它是协

co_wait(c++20)

  •  co_await 运算符暂停执行,直至恢复

co_yield(c++20)

  • 用关键词 co_yield 暂停执行并返回一个值

co_return(c++20)

  • 用关键词 co_return 完成执行并返回一个值

 

decltype(c++11)

说明:在难以或不可能以标准写法进行声明的类型时,decltype 很有用,例如 lambda 相关类型或依赖于模板形参的类型。

语法

decltype ( 实体 )

(1)

(C++11 起)

decltype ( 表达式 )

(2)

(C++11 起)

(1) 若实参为无括号的标识表达式或无括号的类成员访问表达式,则 decltype 产生以此表达式命名的实体的类型。若无这种实体,或该实参指名某个重载函数,则程序非良构。

若实参是指名某个结构化绑定的无括号的标识表达式,则 decltype 产生被引用类型(在关于结构化绑定声明的说明中有所描述)。(C++17 起)

若实参是指名某个非类型模板形参的无括号的标识表达式,则 decltype 生成该模板形参的类型(当该模板形参以占位符类型声明时,则为进行任何所需的类型推导后的类型)。(C++20 起)

(2) 若实参是其他类型为 T 的任何表达式,且

a) 若 表达式 的值类别为亡值,则 decltype 产生 T&&;

b) 若 表达式 的值类别为左值,则 decltype 产生 T&;

c) 若 表达式 的值类别为纯右值,则 decltype 产生 T。

注意如果对象的名字带有括号,则它被当做通常的左值表达式,从而 decltype(x) decltype((x)) 通常是不同的类型。

decltype(auto)      (C++14 起)

类型制约 decltype(auto)    (C++20 起)

 

 

  • 占位类型说明符
  • 检查实体的声明类型,或表达式的类型和值类别。

#include <iostream>

struct A { double x; };

const A* a;

decltype(a->x) y;       // y 的类型是 double(其声明类型)

decltype((a->x)) z = y; // z 的类型是 const double&(左值表达式)

template<typename T, typename U>

auto add(T t, U u) -> decltype(t + u) // 返回类型依赖于模板形参

{                                     // C++14 开始可以推导返回类型

    return t+u;

}

typeid

头文件:<typeinfo>

说明:

用于必须知晓多态对象的动态类型的场合以及静态类型鉴别。

语法

typeid ( 类型 )

(1)

typeid ( 表达式 )

(2)

typeid 表达式为左值表达式,指代一个具有静态存储期的,多态类型 std::type_info 或某个其派生类型的 const 限定版本类型的对象。

解释

1)  指代一个表示 类型 类型的 std::type_info 对象。若 类型 为引用类型,则结果所指代的 std::type_info 对象表示被引用的类型。

2)  检验表达式 表达式

  a)  表达式 为标识某个多态类型(即声明或继承至少一个虚函数的类)对象的泛左值表达式,则 typeid 表达式对该表达式求值,然后指代表示该表达式动态类型的 std::type_info 对象。若该泛左值表达式为通过对一个指针应用一元 * 运算符所得,且该指针为空指针值,则抛出 std::bad_typeid 类型或从 std::bad_typeid 派生的类型的异常。

  b)  表达式 不是多态类型的泛左值表达式,则 typeid 不对该表达式求值,而它所指代的 std::type_info 对象表示该表达式的静态类型。不进行左值到右值、数组到指针或函数到指针转换。然而对于纯右值参数,(形式上)要进行临时量实质化:参数必须在 typeid 表达式所出现的语境中可析构。 (C++17 )在所有情况下,typeid 都忽略顶层的 cv 限定符(即 typeid(T) == typeid(const T))。

 typeid 的操作数为类类型或到类类型的引用,则该类类型必须不是不完整类型

若对处于构造和销毁过程中的对象(在构造函数或析构函数之内,包括构造函数的初始化器列表默认成员初始化器)使用 typeid,则此 typeid 所指代的 std::type_info 对象表示正在构造或销毁的类,即便它不是最终派生类。

const std::type_info& ti1 = typeid(A);

const std::type_info& ti2 = typeid(A);

assert(&ti1 == &ti2); // 不保证

assert(ti1.hash_code() == ti2.hash_code()); // 保证

assert(std::type_index(ti1) == std::type_index(ti2)); // 保证

struct

用法

 

  • 若存在于作用域中的某个函数或变量所拥有的名字,与某个非联合体类类型的名字相同,则可在其名字之前加上 struct 来消歧义,这产生一个详述类型说明符
  • 复合类型的声明

union

用法

 

联合体是特殊的类类型,它在一个时刻只能保有其一个非静态数据成员

联合体声明的类说明符与类或结构体的声明相似:

union attr 类头名 { 成员说明 }

union { 成员说明 } ;

C++ 标准库包含 std::variant,它可取代联合体和联合体式的类的大多数用途

#include <variant>
#include <iostream>
 
int main()
{
    std::variant<char, int, double> s = 'a';
    std::visit([](auto x){ std::cout << x << '\n';}, s);
    s = 123;
    std::visit([](auto x){ std::cout << x << '\n';}, s);
}

enum

语法:

enum-关键词 attr(可选) enum-(可选) enum-(可选)(C++11) { 枚举项列表(可选) } (1)

enum-关键词 attr(可选) enum- enum-(可选) ;   (2)(C++11 起)

 

enum-关键字

enumenum class(C++11 起)  enum struct(C++11 起) 之一

attr(C++11)

-

任意数量的属性的可选序列

enum-名

-

所声明的枚举的名字。若存在,且若此声明为重声明,则其之前可带有 嵌套名说明符(C++11 起),即名字和作用域解析运算符 :: 的序列并以作用域解析运算符结尾。仅可在无作用域枚举声明中省略名字

enum-基(C++11)

-

冒号 (:),后随指名某个整型类型的 类型说明符序列(若它为 cv 限定,则忽略其限定性),该类型将作为此枚举类型的固定底层类型

枚举项列表

-

枚举项定义的逗号分隔列表,每项要么是简单的 标识符,它成为枚举项之名,要么是带初始化器的标识符:标识符 = 常量表达式

有两种截然不同的枚举:无作用域枚举(以 enum-关键词 enum 声明)和有作用域枚举(以 enum-关键词 enum class 或 enum struct 声明)。

  • 无作用域枚举

enum 名字 { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , ... }

(1)

enum 名字 : 类型 { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , ... }

(2)

(C++11 起)

enum 名字 : 类型 ;

(3)

(C++11 起)

1) 声明无作用域枚举类型,其底层类型不固定(此情况中,底层类型是由实现定义的某个能表示所有枚举项值的整型类型;此类型不大于 int,除非枚举项的值不能放入 int 或 unsigned int。若 枚举项列表 为空,则底层类型为如同枚举拥有单个值为 0 的枚举项)。

2) 声明底层类型固定的无作用域枚举类型。

3) 无作用域枚举的不可见枚举声明必须指定底层类型。

每个 枚举项 都成为该枚举类型(即 名字)的一个具名常量,在其外围作用域可见,且可用于要求常量的任何位置。整数、浮点和枚举类型的值,可用 static_cast 或显式转型转换到任何枚举类型。

无作用域枚举的 名字 可以忽略:这种声明仅将各枚举项引入到其外围作用域中:

  • 有作用域枚举

enum struct|class 名字 { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , ... }

(1)

enum struct|class 名字 : 类型 { 枚举项 = 常量表达式 , 枚举项 = 常量表达式 , ... }

(2)

enum struct|class 名字 ;

(3)

enum struct|class 名字 : 类型 ;

(4)

1) 声明底层类型为 int 的有作用域枚举类型(关键词 class 与 struct 完全等价)

2) 声明底层类型为 类型 的有作用域枚举类型

3) 底层类型为 int 的有作用域枚举类型的不可见枚举声明

4) 底层类型为 类型 的有作用域枚举类型的不可见枚举声明

每个 枚举项 都成为该枚举的类型(即 名字)的具名常量,它为该枚举的作用域所包含,且可用作用域解析运算符访问。没有从有作用域枚举项到整数类型的隐式转换,尽管 static_cast 可用于获得枚举项的数值。

  • using enum 声明

using enum 嵌套名说明符(可选) 名字 ;

(C++20 起)

using enum 声明引入其所指名的枚举的枚举项名字,如同用对每个枚举项的 using 声明。在类作用域中时, using enum 声明将其所指名的枚举的枚举项名字作为成员添加到作用域,使成员查找能访问它们

 

(1)无作用域枚举

enum access_t { read = 1, write = 2, exec = 4 }; //枚举项:1、2、4 范围:0..7
access_t rwe = static_cast<access_t>(7);
assert((rwe & read) && (rwe & write) && (rwe & exec));
 
access_t x = static_cast<access_t>(8.0); // C++17 起为未定义行为
access_t y = static_cast<access_t>(8); // C++17 起为未定义行为
 
enum foo { a = 0, b = UINT_MAX }; // 范围:[0, UINT_MAX]
foo x= foo(-1); // C++17 起为未定义行为,即使 foo 的底层类型为 unsigned int

(2)有作用域枚举

enum class Color { red, green = 20, blue };
Color r = Color::blue;
switch(r)
{
    case Color::red  : std::cout << "red\n";   break;
    case Color::green: std::cout << "green\n"; break;
    case Color::blue : std::cout << "blue\n";  break;
}
// int n = r; // 错误:不存在从有作用域枚举到 int 的转换
int n = static_cast<int>(r); // OK, n = 21

enum byte : unsigned char {}; // byte 是新的整数类型
byte b { 42 }; // C++17 起 OK(直接列表初始化)

(3)using enum 声明

enum class fruit { orange, apple };
struct S {
  using enum fruit; // OK :引入 orange 与 apple 到 S 中
};
void f()
{
    S s;
    s.orange;  // OK :指名 fruit::orange
    S::orange; // OK :指名 fruit::orange
}

typedef 

typedef - 创建能在任何位置替代(可能复杂的)类型名的别名。

  • 说明:

 

using

用法

命名空间

将别处定义的名字引入到此 using 声明所出现的声明区中。

using typename(可选) 嵌套名说明符 无限定标识 ;

(C++17 前)

using 声明符列表 ;

(C++17 起)

typename

-

在 using 声明从基类向类模板中引入成员类型时,关键词 typename 是用于解决待决名所必须的

解释

using 声明可用于将命名空间成员引入到另一命名空间与块作用域,或将基类成员引入到派生类定义中,或将枚举项引入命名空间、块或类作用域中 

有多于一个 using 声明符的 using 声明,等价于有单个 using 声明符的 using 声明的对应序列。

(C++17 起)

在命名空间和块作用域中

using 声明将另一命名空间的成员引入到当前命名空间或块作用域中-->using std::string;

在类定义中

using 声明将基类成员引入到派生类的定义中,例如将基类的受保护成员暴露为派生类的公开成员。此情况下 嵌套名说明符 必须指名所定义的类的某个基类。若其名字是该基类的某个重载的成员函数的名字,则具有该名字的所有基类成员函数均被引入。若派生类已有具有相同名字、形参列表和限定的成员,则派生类成员隐藏或覆盖从基类引入的成员(不与之冲突)。

继承构造函数

若 using 声明指代正在定义的类的某个直接基类的构造函数(例如 using Base::Base;),则在初始化派生类时,令该基类的所有构造函数(忽略成员访问)均对重载决议可见。

引入有作用域枚举项

 using 声明亦能将枚举的枚举项引入命名空间、块和类作用域。using 声明亦能用于无作用域枚举项。

using 标识符 attr(可选) = 类型标识 ;

(1)

template < 模板形参列表 >

using 标识符 attr(可选) = 类型标识 ;

(2)

解释

1) 类型别名声明引入一个名字,可用做 类型标识 所指代的类型的同意词。它不引入新类型,且不能更改既存类型名的含义。类型别名声明和 typedef 声明之间无区别。此声明可出现于块作用域、类作用域或命名空间作用域。

2) 别名模板是一种模板,当其特化时等价于以别名模板的模板实参来替换 类型标识 中的模板形参的结果。

 

在类定义中

#include <iostream>
struct B {
    virtual void f(int) { std::cout << "B::f\n"; }
    void g(char)        { std::cout << "B::g\n"; }
    void h(int)         { std::cout << "B::h\n"; }
 protected:
    int m; // B::m 为受保护
    typedef int value_type;
};
 
struct D : B {
    using B::m; // D::m 为公开
    using B::value_type; // D::value_type 为公开
    using B::f;
    void f(int) { std::cout << "D::f\n"; } // D::f(int) 覆盖 B::f(int)
    using B::g;
    void g(int) { std::cout << "D::g\n"; } // g(int) 与 g(char) 均作为 D 成员可见
    using B::h;
    void h(int) { std::cout << "D::h\n"; } // D::h(int) 隐藏 B::h(int)
};
int main()
{
    D d;
    B& b = d;
//    b.m = 2; // 错误,B::m 受保护
    d.m = 1; // 受保护的 B::m 可作为公开的 D::m 访问
    b.f(1); // 调用派生类 f()
    d.f(1); // 调用派生类 f()
    d.g(1); // 调用派生类 g(int)
    d.g('a'); // 调用基类 g(char)
    b.h(1); // 调用基类 h()
    d.h(1); // 调用派生类 h()
}

继承构造函数

struct B1 {  B1(int, ...) { } };
struct B2 {  B2(double)   { } };
 
int get();
 
struct D1 : B1 {
  using B1::B1;  // 继承 B1(int, ...)
  int x;
  int y = get();
};
 
void test() {
  D1 d(2, 3, 4); // OK:B1 通过调用 B1(2, 3, 4) 初始化,
                 // 然后 d.x 被默认初始化(不进行初始化),
                 // 然后 d.y 通过调用 get() 初始化
  D1 e;          // 错误:D1 无默认构造函数
}

引入有作用域枚举项

enum class button { up, down };
struct S {
    using button::up;
    button b = up; // OK
};

typename

用法

 

  • 模板声明中,typename 可用作 class 的代替品,以声明类型模板形参和模板形参 (C++17 )
  • 在模板的声明或定义内,typename 可用于声明某个待决的有限定名是类型。
  • 在模板的声明或定义内, (C++11 )typename 可在非待决的有限定类型名之前使用。此情况下它没有效果。
  • 在类型要求的要求中。(C++20 起)

 

namespace

见以下:c++声明中 namespace 说明

 

C++ 声明

namespace

说明:

用法

命名空间提供了在大项目中避免名字冲突的一种方法

命名空间别名允许程序员定义命名空间的另一个名字。它们常用作长的或嵌套过深的命名空间的简便使用方式。

namespace 命名空间名 { 声明序列 }

(1)

inline namespace 命名空间名 { 声明序列 }

(2)

(C++11 起)

namespace { 声明序列 }

(3)

命名空间名::名字

(4)

using namespace 命名空间名;

(5)

using 命名空间名::名字;

(6)

namespace 名字 = 有限定命名空间 ;

(7)

namespace 命名空间名::inline(C++20 起)(可选) 名字 { 声明序列 }

(8)

(C++17 起)

1) 命名空间 命名空间名 具名命名空间定义

2) 内联命名空间:命名空间名 内的声明将在其外围命名空间可见。

3) 无名命名空间定义。其成员具有从声明点到翻译单元结尾的潜在作用域,并具有内部连接

4) 命名空间名(还有类名)可以出现在作用域解析运算符的左侧,作为有限定的名字查找的一部分。

5) using 指令:从 using 指令之后到指令出现的作用域结尾为止,以对任何名字的无限定名字查找的视点来说,来自 命名空间名 的任何名字均可见,如同它声明于同时含有该 using 指令和 命名空间名 两者的最接近外围命名空间作用域一样。

6) using 声明:令来自命名空间 命名空间名 的符号 名字 无限定名字查找可见,如同将它声明于包含该 using 声明的相同的类作用域、块作用域或命名空间之中一样。

7) 命名空间别名定义(namespace-alias-definition:令 名字 成为另一命名空间的同义词:见命名空间别名

8) 嵌套命名空间定义:namespace A::B::C { ... } 等价于 namespace A { namespace B { namespace C { ... } } }。

namespace A::B::inline C { ... } 等价于 namespace A::B { inline namespace C { ... } }。inline 可出现于除首个以外的每个命名空间名之前:namespace A::inline B::C {} 等价于 namespace A { inline namespace B { namespace C {} } }。

(C++20 起)

语法

namespace 别名 = 命名空间名;

(1)

namespace 别名 = ::命名空间名;

(2)

namespace 别名 = 嵌套名::命名空间名;

(3)

别名 必须是先前未使用过的名称。别名 在引入它的作用域的期间内有效。

  • 命名空间别名
  • 命名空间语法

namespace Q {
  namespace V { // V 是 Q 的成员,且完全在 Q 内定义
// namespace Q::V { // C++17 中对上述二行的替代写法
    class C { void m(); }; // C 是 V 的成员且完全定义于 V 内
                           // C::m 仅声明
    void f(); // f 是 V 的成员,但只在此声明
  }
  void V::f() // V 的成员 f 的 V 外定义
              // f 的外围命名空间仍是全局命名空间、Q 与 Q::V
  {
      extern void h(); // 这声明 ::Q::V::h
  }
  void V::C::m() // V::C::m 的命名空间(及类)外定义
                 // 外围命名空间是全局命名空间、Q 与 Q::V
  {
  }
}

namespace A {
    int x;
}
namespace B {
    int i;
    struct g { };
    struct x { };
    void f(int);
    void f(double);
    void g(char); // OK:函数名 g 隐藏类 g
}
void func() {
    int i;
    using B::i; // 错误:两次声明 i
 
    void f(char);
    using B::f; // OK:f(char)、f(int)、f(double) 是重载
    f(3.5); // 调用 B::f(double)
 
    using B::g;
    g('a');      // 调用 B::g(char)
    struct g g1; // 声明 g1 拥有类型 struct B::g
 
    using B::x;
    using A::x;  // OK :隐藏 B::x
    x = 99;      // 赋值给 A::x
    struct x x1; // 声明 x1 拥有类型 struct B::x
}

别名:

#include <iostream>

namespace foo {

    namespace bar {

         namespace baz {

             int qux = 42;

         }

    }

}

namespace fbz = foo::bar::baz;

int main()

{

    std::cout << fbz::qux << '\n';

}

 

 

 

 

 

 

 

 

 

 

 

 

 <type_traits>

template< class T >

struct is_trivially_copyable;

trivally copyable 需要满足3个条件:

  1. 连续的内存空间
  2. 拷贝构造函数需要拷贝全部的bits (memcpy)
  3. 没有虚函数和 noexcept constructor

std::alignment_of(c++11)

std::alignment_of_v (C++17 )

提供等于 T 类型对齐要求的成员常量 value ,如同用 alignof 表达式获得。

#include <iostream>
#include <type_traits>
 
class A {};
 
int main()
{
    std::cout << std::alignment_of<A>::value << '\n';
    std::cout << std::alignment_of<int>() << '\n'; // 另一种语法
    std::cout << std::alignment_of_v<double> << '\n'; // c++17 另一种语法
}

 

 

 

std::remove_all_extents_t<T>

 

<ctime>

 

size_t

 

is_trivial

 

Explicit

 

override

 

snprintf

 

calloc

 

strdup

 

sqrt

 

Std::log

 

Math_errhandling

 

wchar

 

Char32_t

 

Char16_t

 

.h/.hpp/.hxx

 

 

 

Numeric_limit

 

epsilon

 

Initializer_list

 

stack

 

final

 

For_each

 

algorithm

 

throw

throw std::runtime_error( "File cannot be opened")

 

std::invalid_argument

logic_error

bad_typeid

 

bad_cast

 

bad_array_new_length

 

bad_alloc

 

std::system_error

 

bad_exception

 

std::out_of_range

 

std::exception

 

throw(std::runtime_error)

 

std::terminate()

 

std::is_nothrow_move_constructible

 

std::atexit

 

 std::at_quick_exit

Logic_error

 

 

 

lambda

 

copy

 

sort

 

transform

 

Make_shared

 

move

 

Forward(cv)

 

Lock_guard

 

<mutex>

 

constexpr

 

defaule

 

register

 

inline

 

asm

 

posix

 

is_arithmetic

 

[[noreturn]]

 

dlsym

 

拖尾返回类型->type

 

左右值引用

 

accumulate

 

Chrono::

Seconds

System_clock

Time_point

 

utility

 

Is_copy

 

智能指针

std::unique_ptr

类型的移动构造,移动赋值,"转换"移动构造和"转换"移动赋值

td::make_unique<A[]>(10)

const_iterator

 

std::shared_ptr

 

std::weak_ptr

 

std::basic_ios类型的std::move()

 

std::basic_filebuf

多线程

std::thread

 

std::unique_lock

 

std::shared_lock

 

std::promise

 

std::future

 

std::shared_future

 

std::packaged_task

 

std::is_copy_constructible

 

std::is_trivially_move_constructible

 

std::is_trivially_copy_assignable

 

std::is_trivially_move_assignable

重载

operator delete

 

编译宏

#define subs(x) a ## x         //合规

 

#define A(x) #x         // 合规

 

defined, __LINE__, __FILE__, __DATE__, __TIME__, __STDC__, errno 和assert。

不能使用setjmp宏和longjmp函数。

<cstring>

<clocale> (locale.h)setlocale函数

std::hash

std::new_handler

offsetof

Placement new

std::bind

<cctype>

isdigit和isxdigit

<algorithm>

<cstdint>

const_iterator

equal_range

std::distance

<random>

std::srand(std::time(nullptr))

std::random_device rd;

        std::default_random_engine eng{ rd() };

        std::uniform_int_distribution<int> ud{ 0, 100 };

 

<cstdio>

fgetpos, fopen, ftell, gets, perror, remove, rename

basic_filebuf<charT, traits>

fstream

fseek、fsetpos或rewind

结构化绑定声明(c++17)

说明

类似引用,结构化绑定是既存对象的别名。不同于引用的是,结构化绑定的类型不必为引用类型。

结构化绑定声明将 标识符列表 中的所有标识符,引入作为其外围作用域中的名字,并将它们绑定到

表达式 所指代的对象的各个子对象或元素。以此方式引入的绑定被称作结构化绑定。

attr(可选) cv-auto ref-运算符(可选) [ 标识符列表 ] = 表达式 ;(1)

attr(可选) cv-auto ref-运算符(可选) [ 标识符列表 ] { 表达式 } ;(2)

attr(可选) cv-auto ref-运算符(可选) [ 标识符列表 ] ( 表达式 ) ;(3)

说明:

attr

-

任意数量的属性的序列

cv-auto

-

可有 cv 限定的 auto 类型说明符,亦可包含存储类说明符 static 或 thread_local ;在 cv 限定符中包含 volatile 是被弃用的 (C++20 )

ref-运算符

-

& 或 && 之一

标识符表

-

此声明所引入的各标识符的逗号分隔的列表

表达式

-

顶层没有逗号运算符的表达式(文法上为赋值表达式),且具有数组或非联合类之一的类型。

情况 1:绑定数组

标识符列表 中的每个标识符均成为指代数组的对应元素的左值。标识符的数量必须等于数组的元素数量。

int a[2] = {1,2};

auto [x,y] = a; // 创建 e[2],复制 a 到 e,然后 x 指代 e[0],y 指代 e[1]

auto& [xr, yr] = a; // xr 指代 a[0],yr 指代 a[1]

情况 2:绑定元组式类型

表达式 std::tuple_size<E>::value 必须是良构的整数常量表达式,且标识符的数量必须等于 std::tuple_size<E>::value。

示例:

float x{};

char  y{};

int   z{};

std::tuple<float&,char&&,int> tpl(x,std::move(y),z);

const auto& [a,b,c] = tpl;

// a 指名指代 x 的结构化绑定;decltype(a) 为 float&

// b 指名指代 y 的结构化绑定;decltype(b) 为 char&&

// c 指名指代 tpl 的第 3 元素的结构化绑定;decltype(c) 为 const int

情况 3:绑定到数据成员

struct S {

    int x1 : 2;

    volatile double y1;

};

S f();

const auto [x, y] = f(); // x 是标识 2 位位域的 const int 左值

                         // y 是 const volatile double 左值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值