2022-Anthony Calandra-C++ 11 Features
C++ 11
- C++ 11
- 特性弃用
- C++11 Language Features
- Move semantics
- Rvalue references
- Forwarding references
- Variadic templates
- Initializer lists
- Static assertions
- auto
- Lambda expressions
- decltype
- Type aliases
- nullptr
- Strongly-typed enums
- Attributes
- constexpr
- Delegating constructors
- User-defined literals
- Explicit virtual overrides
- Final specifier
- Default functions
- Deleted functions
- Range-based for loops
- Special member functions for move semantics
- Converting constructors
- Explicit conversion functions
- Inline namespaces
- Non-static data member initializers
- Right angle brackets
- Ref-qualified member functions
- Trailing return types
- Noexcept specifier
- C++11 Library Features
- Acknowledgements
- std::move
- std::forward
- std::thread
- std::to_string
- type traits
- smart pointers
- std::chrono
- tuples
- std::tie
- std::array
- unordered containers
- std::make_shared
- std::ref
- memory model
- std::async
- std::begin/end
特性弃用
在学习
注意:弃用不等于废弃,只是用于暗示程序员这些特性将从未来的标准中消失,应该尽量避免使用。但是,已弃用的特性依然是标准库的一部分,并且出于兼容性的考虑,这些特性其实会永久保留。
- 如果一个类有析构函数,为其生成拷贝构造函数和拷贝赋值运算符的特性被弃用了。
- 不再允许字符串字面值常量赋值给一个
char * 。如果需要用字符串字面值常量赋值和初始化一个char * ,应该使用const char * 或者auto 。
char *str = "hello world!"; // 将出现弃用警告
C++98 异常说明、unexcepted_handler、set_unexpected() 等相关特性被弃用,应该使用noexcept 。auto_ptr 被弃用,应使用unique_ptr 。register 关键字被弃用。bool 类型的++ 操作被弃用。C 语言风格的类型转换被弃用,应该使用static_cast 、reinterpret_cast、const_cast 来进行类型转换。
还有一些其他诸如参数绑定(std::bind
和 std::function
export
等特性也均被弃用。前面提到的这些特性如果你从未使用或者听说过,也请不要尝试去了解他们,应该向新标准靠拢,直接学习新特性。毕竟,技术是向前发展的。
C++11 Language Features
Move semantics
移动语义
移动一个对象意味着转移某些资源的所有权给另一个对象去管理。
移动语义的首个收益就是性能优化。
当一个对象的生命到达终点时,或临时对象,或显式调用std::move
std::vector
仅仅拷贝一些指针和内部状态到新向量vector
中的每一个元素,如果这个被拷贝的vector
马上被销毁,那么“拷贝”的代价是很高昂的且不是必要的。
移动也允许非复制类型如std::unique_ptr
查看相关部分std::move
, std::forward
, forwarding references
.
Rvalue references
右值引用
T&&
来创建一个非int
,或用户自定义类型T
的右值引用。右值引用只会绑定右值。
左值和右值的描述:
int x = 0; // `x` is an lvalue of type `int`
int& xl = x; // `xl` is an lvalue of type `int&`
int&& xr = x; // compiler error -- `x` is an lvalue
int&& xr2 = 0; // `xr2` is an lvalue of type `int&&` -- binds to the rvalue temporary, `0`
注意:这里要区分好右值和绑定右值的左值
查看std::move
, std::forward
, forwarding references
.
Forwarding references
转发引用
也被称为万能引用。转发引用的创建方式有两种:当T
是T&&
语法被创建;或者使用auto&&
。这使得完美转发成为可能:传递参数同时维护其值类别的能力
转发引用允许一个引用既可以绑定左值类型又可以绑定右值类型。转发引用遵循引用坍缩规则:
T& &
坍缩成T&
T& &&
坍缩成T&
T&& &
坍缩成T&
T&& &&
坍缩成T&&
auto
类型推导左值和右值
int x = 0; // `x` is an lvalue of type `int`
auto&& al = x; // `al` is an lvalue of type `int&` -- binds to the lvalue, `x`
auto&& ar = 0; // `ar` is an lvalue of type `int&&` -- binds to the rvalue temporary, `0`
// Since C++14 or later:
void f(auto&& t) {
// ...
}
// Since C++11 or later:
template <typename T>
void f(T&& t) {
// ...
}
int x = 0;
f(0); // deduces as f(int&&)
f(x); // deduces as f(int&)
int& y = x;
f(y); // deduces as f(int& &&) => f(int&)
int&& z = 0; // NOTE: `z` is an lvalue with type `int&&`.
f(z); // deduces as f(int&& &) => f(int&)
f(std::move(z)); // deduces as f(int&& &&) => f(int&&)
查看std::move
, std::forward
, rvalue references
.
Variadic templates
变参模板
...
语法用来创建或展开一个参数包。模板参数包是一个接受
template <typename... T>
struct arity {
constexpr static int value = sizeof...(T);
};
static_assert(arity<>::value == 0);
static_assert(arity<char, short, int>::value == 3);
一个有趣的用途是从一个参数包创建一个初始化器列表,以便遍历可变参数。
template <typename First, typename... Args>
auto sum(const First first, const Args... args) -> decltype(first) {
const auto values = {first, args...};
return std::accumulate(values.begin(), values.end(), First{0});
}
sum(1, 2, 3, 4, 5); // 15
sum(1, 2, 3); // 6
sum(1.5, 2.0, 3.7); // 7.2
tuple
就是用变参模板实现的,其能存储不同类型的值,这里只是简单介绍它不定长的特点。
Initializer lists
初始化列表
使用{1, 2, 3}
创建了一个整数的序列,类型是std::initializer_list<int>
。用std::initializer_list<int>
来替换传递给函数的vector
int sum(const std::initializer_list<int>& list) {
int total = 0;
for (auto& e : list) {
total += e;
}
return total;
}
auto list = {1, 2, 3};
sum(list); // == 6
sum({1, 2, 3}); // == 6
sum({}); // == 0
Static assertions
静态断言
静态断言在编译器就会被评估。
constexpr int x = 0;
constexpr int y = 1;
static_assert(x == y, "x != y");
auto
auto
auto a = 3.14; // double
auto b = 1; // int
auto& c = b; // int&
auto d = { 0 }; // std::initializer_list<int>
auto&& e = 1; // int&&
auto&& f = b; // int&
auto g = new auto(123); // int*
const auto h = 1; // const int
auto i = 1, j = 2, k = 3; // int, int, int
auto l = 1, m = true, n = 1.61; // error -- `l` deduced to be int, `m` is bool
auto o; // error -- `o` requires initializer
auto
对可读性及其有益,尤其是特别复杂的类型。
std::vector<int> v = ...;
std::vector<int>::const_iterator cit = v.cbegin();
// vs.
auto cit = v.cbegin();
函数也可以使用auto
来进行返回类型的推导。在decltype
。
template <typename X, typename Y>
auto add(X x, Y y) -> decltype(x + y) {
return x + y;
}
add(1, 2); // == 3
add(1, 2.0); // == 3.0
add(1.5, 1.5); // == 3.0
上面例子中的后置返回类型是表达式x + y
所声明的类型。举个例子,如果x
是个整形,y
是decltype(x + y)
是x + y
的类型来推导返回类型。
注意:后置返回类型只能访问函数参数,也可调用参数的this
相关功能
Lambda expressions
lambda
是一个可以在当前作用域中捕捉变量的无名函数对象。
它由五部分组成:捕捉列表,参数列表,函数属性,后置返回类型,函数体。其中
一个完整的例子如下:
auto func = []()mutable -> int { return 1; };
// [] 为捕捉列表
// ()为参数列表,当空可省略
// mutable,表示按值传递捕捉列表,可以修改捕捉列表中的变量,但不会影响外部变量,当空可省略。当其存在时,参数列表必须也存在。
// -> int 后置返回类型,如果是 void 可省略。
// { return 1; }; 函数体
捕捉列表如下
[]
- 什么也不捕捉,表示函数完全用不到当前作用域的其他变量[=]
- 捕捉当前作用域内函数定义之前的所有局部变量( 如果当前所处在一个函数中,那么参数也算) ,但是以值传递来使用。[&]
- 作用同上,但是捕捉变量以引用形式进行传递。[this]
- 捕获当前lambda 所在的对象的this 指针。[a]
- 将a 按照值进行传递。[&a]
- 将a 按照引用传递。[a, &b]
- 将a 按值传递,b 按引用传递[=,&a,&b]
- 除a 和b 按引用进行传递外,其他参数都按值进行传递。[&,a,b]
- 除a 和b 按值进行传递外,其他参数都按引用进行传递。
注意:捕捉的范围是当前作用域内
int x = 1;
auto getX = [=] { return x; };
getX(); // == 1
auto addX = [=](int y) { return x + y; };
addX(1); // == 2
auto getXRef = [&]() -> int& { return x; };
getXRef(); // int& to `x`
默认情况下,按值捕捉的变量在const
,mutable
关键字允许修改捕捉变量。关键字被置在参数列表后面mutable
而存在
int x = 1;
auto f1 = [&x] { x = 2; }; // OK: x is a reference and modifies the original
auto f2 = [x] { x = 2; }; // ERROR: the lambda can only perform const-operations on the captured value
// vs.
auto f3 = [x]() mutable { x = 2; }; // OK: the lambda can perform any operations on the captured value
decltype
decltype
是一个用来返回表达式声明类型的操作,其也会维护表达式中的
decltype
例子
int a = 1; // `a` is declared as type `int`
decltype(a) b = a; // `decltype(a)` is `int`
const int& c = a; // `c` is declared as type `const int&`
decltype(c) d = a; // `decltype(c)` is `const int&`
decltype(123) e = 123; // `decltype(123)` is `int`
int&& f = 1; // `f` is declared as type `int&&`
decltype(f) g = 1; // `decltype(f) is `int&&`
decltype((a)) h = g; // `decltype((a))` is int&
template <typename X, typename Y>
auto add(X x, Y y) -> decltype(x + y) {
return x + y;
}
add(1, 2.0); // `decltype(x + y)` => `decltype(3.0)` => `double`
注意:decltype(a)
和decltype((a))
,很容易定义错类型,建议只在模板函数的返回值中使用。
相关查看decltype(auto)
Type aliases
类型别名
在语义上和typedef
相近,但是using
更为简洁,且兼容模板。
template <typename T>
using Vec = std::vector<T>;
Vec<int> v; // std::vector<int>
using String = std::string;
String s {"foo"};
nullptr
空指针
NUll
宏。nullptr
的类型是std::nullptr_t
bool
类型。但是nullptr
不可以隐式的转成整形,这是nullptr
和NULL
的最大不同,解决了NULL
在特定情况下的调用歧义问题。
void foo(int);
void foo(char*);
foo(NULL); // error -- ambiguous
foo(nullptr); // calls foo(char*)
Strongly-typed enums
强类型枚举
类型安全枚举解决了
// Specifying underlying type as `unsigned int`
enum class Color : unsigned int { Red = 0xff0000, Green = 0xff00, Blue = 0xff };
// `Red`/`Green` in `Alert` don't conflict with `Color`
enum class Alert : bool { Red, Green };
Color c = Color::Red;
Attributes
__attribute__(...)
__declspec
等属性提供了通用语法。
// `noreturn` attribute indicates `f` doesn't return.
[[ noreturn ]] void f() {
throw "error";
}
constexpr
常量表达式
常量表达式是编译器在编译时计算的表达式。在常量表达式中,只有简单的计算才可以被执行。使用constexpr
来将一个变量,函数等指定为常量表达式。
constexpr int square(int x) {
return x * x;
}
int square2(int x) {
return x * x;
}
int a = square(2); // mov DWORD PTR [rbp-4], 4
int b = square2(2); // mov edi, 2
// call square2(int)
// mov DWORD PTR [rbp-8], eax
constexpr
变量要在编译期就被计算出来
const int x = 123;
constexpr const int& y = x; // error -- constexpr variable `y` must be initialized by a constant expression
类使用常量表达式
struct Complex {
constexpr Complex(double r, double i) : re{r}, im{i} { }
constexpr double real() { return re; }
constexpr double imag() { return im; }
private:
double re;
double im;
};
constexpr Complex I(0, 1);
Delegating constructors
委托构造
构造函数可以使用初始化列表来调用当前类中其他的构造函数。
struct Foo {
int foo;
Foo(int foo) : foo{foo} {}
Foo() : Foo(0) {}
};
Foo foo;
foo.foo; // == 0
User-defined literals
用户自定义字面值
用户自定义字面值允许你扩展语言,添加你自己的语法。定义一个T operator "" X(...) { ... }
,返回值是T
类型,名字是X
的函数。名字X
就是你要使用的字面值。X
要以下划线起始,否则不会被调用。函数的参数类型有以下几个选项:unsigned long long
,long double
,char
,wchar_t
,char16_t
,char32_t
,const char *
例子
// `unsigned long long` parameter required for integer literal.
long long operator "" _celsius(unsigned long long tempCelsius) {
return std::llround(tempCelsius * 1.8 + 32);
}
24_celsius; // == 75
例子
// `const char*` and `std::size_t` required as parameters.
int operator "" _int(const char* str, std::size_t) {
return std::stoi(str);
}
"123"_int; // == 123, with type `int`
// 作用同上
int operator "" _int(const char* str) {
return std::stoi(str);
}
123_int; // == 123, with type `int`
Explicit virtual overrides
显式虚函数重写
指定一个虚函数重写于另一个虚函数。使用override
但是虚函数并没有重写基类虚函数的话,会提示编译错误。
struct A {
virtual void foo();
void bar();
};
struct B : A {
void foo() override; // correct -- B::foo overrides A::foo
void bar() override; // error -- A::bar is not virtual
void baz() override; // error -- B::baz does not override A::baz
};
Final specifier
指定当前虚函数不可以在派生类中被重写,或指定当前类不可被继承。
虚函数不可被重写:
struct A {
virtual void foo();
};
struct B : A {
virtual void foo() final;
};
struct C : B {
virtual void foo(); // error -- declaration of 'foo' overrides a 'final' function
};
类不可被继承:
struct A final {};
struct B : A {}; // error -- base 'A' is marked 'final'
Default functions
默认函数
提供一种更优雅,更有效的函数的默认实现方式,比如构造函数:
struct A {
A() = default;
A(int x) : x{x} {}
int x {1};
};
A a; // a.x == 1
A a2 {123}; // a.x == 123
继承
struct B {
B() : x{1} {}
int x;
};
struct C : B {
// Calls B::B
C() = default;
};
C c; // c.x == 1
Deleted functions
删除函数
提供一种更优雅,更有效的函数的删除实现方式。如阻止对象的拷贝:
class A {
int x;
public:
A(int x) : x{x} {};
A(const A&) = delete;
A& operator=(const A&) = delete;
};
A x {123};
A y = x; // error -- call to deleted copy constructor
y = x; // error -- operator= deleted
Range-based for loops
可迭代容器的循环语法糖。
std::array<int, 5> a {1, 2, 3, 4, 5};
for (int& x : a) x *= 2;
// a == { 2, 4, 6, 8, 10 }
注意int
和int&
的区别。
std::array<int, 5> a {1, 2, 3, 4, 5};
for (int x : a) x *= 2;
// a == { 1, 2, 3, 4, 5 }
Special member functions for move semantics
移动构造函数
当复制操作发生时,拷贝构造函数和拷贝赋值运算符会被调用。
struct A {
std::string s;
A() : s{"test"} {}
A(const A& o) : s{o.s} {}
A(A&& o) : s{std::move(o.s)} {}
A& operator=(A&& o) {
s = std::move(o.s);
return *this;
}
};
A f(A a) {
return a;
}
A a1 = f(A{}); // move-constructed from rvalue temporary
A a2 = std::move(a1); // move-constructed using std::move
A a3 = A{};
a2 = std::move(a3); // move-assignment using std::move
a1 = f(A{}); // move-assignment from rvalue temporary
Converting constructors
转换构造
{}
来初始化所有对象。
编译器会构造一个类似tuple
的容器,然后看当前类是否带有初始化列表的构造函数,如果有且数据类型能对应上,那么直接调用
struct A {
A(int, int) { cout << "ii" << endl; }
A(initializer_list<int> il) { cout << "il" << endl; }
};
A a {0, 0}; // calls A::A(initializer_list<int> il)
A b (0, 0); // calls A::A(int, int)
如果对应不上的类型都是由精度缺失引起的double
但初始化列表类型是int
struct A {
A(int, double) { cout << "id" << endl; }
A(initializer_list<int> il) { cout << "il" << endl; }
};
A a {0, 0.5}; // error! 'double' cannot be narrowed to 'int'
A b (0, 0.5); // calls A::A(int, double)
如果对应不上的类型完全不一致int
但传入的string
struct A {
A(int, double, string) { cout << "ids" << endl; }
A(initializer_list<int>) { cout << "il" << endl; }
};
A a {0, 0.5, "abc"}; // calls A::A(int, double, string)
A b {1, 2, 3}; // calls A::A(initializer_list<int>)
struct A {
A(int) {}
};
A a(1.1); // OK
A b {1.1}; // Error narrowing conversion from double to int
Explicit conversion functions
显式转换函数
转换函数可以通过explicit
标识符来置位显式声明,禁止隐式转换。
struct A {
operator bool() const { return true; }
};
struct B {
explicit operator bool() const { return true; }
};
A a;
if (a); // OK calls A::operator bool()
bool ba = a; // OK copy-initialization selects A::operator bool()
B b;
if (b); // OK calls B::operator bool()
bool bb = b; // error copy-initialization does not consider B::operator bool()
bool bbb = bool(b); // OK
Inline namespaces
内联命名空间
将内联名称空间的所有成员视为其父名称空间的一部分,从而允许函数的专门化并简化版本控制过程。 这是一个传递属性,如果
将内联命名空间的所有成员视为它们是在其父亲的命名空间下,从而使函数专有化
namespace Program {
namespace Version1 {
int getVersion() { return 1; }
bool isFirstVersion() { return true; }
}
inline namespace Version2 {
int getVersion() { return 2; }
}
}
int version {Program::getVersion()}; // Uses getVersion() from Version2
int oldVersion {Program::Version1::getVersion()}; // Uses getVersion() from Version1
bool firstVersion {Program::isFirstVersion()}; // Does not compile when Version2 is added
Non-static data member initializers
允许在声明非静态数据成员的地方初始化它们,但构造函数中重复赋值会覆盖他们。
// C++11 之前
class Human {
Human() : age{0} {}
private:
unsigned age;
};
// C++11 及之后
class Human {
private:
unsigned age {0};
};
Right angle brackets
右角括号
从
typedef std::map<int, std::map <int, std::map <int, int> > > cpp98LongTypedef;
typedef std::map<int, std::map <int, std::map <int, int>>> cpp11LongTypedef;
Ref-qualified member functions
引用限定成员函数
相同的成员函数函数,现在可以根据*this
是左值还是右值引用来调用不同的重载。
struct Bar {
// ...
};
struct Foo {
Bar getBar() & { return bar; }
Bar getBar() const& { return bar; }
Bar getBar() && { return std::move(bar); }
private:
Bar bar;
};
Foo foo{};
Bar bar = foo.getBar(); // calls `Bar getBar() &`
const Foo foo2{};
Bar bar2 = foo2.getBar(); // calls `Bar Foo::getBar() const&`
Foo{}.getBar(); // calls `Bar Foo::getBar() &&`
std::move(foo).getBar(); // calls `Bar Foo::getBar() &&`
std::move(foo2).getBar(); // calls `Bar Foo::getBar() const&&`
Trailing return types
后置返回类型
int f() {
return 123;
}
// vs.
auto f() -> int {
return 123;
}
auto g = []() -> int {
return 123;
};
当返回类型不能被立即确定时,这个特点会非常有用。
// NOTE: This does not compile!
template <typename T, typename U>
decltype(a + b) add(T a, U b) {
return a + b;
}
// Trailing return types allows this:
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
在decltype(auto)
来替代。
Noexcept specifier
无异常说明符
noexcept
说明符指定一个函数是否会返回异常throw()
的进阶版。
void func1() noexcept; // does not throw,一般用这个就够了
void func2() noexcept(true); // does not throw
void func3() throw(); // does not throw
void func4() noexcept(false); // may throw
std::terminate
来结束程序。
extern void f(); // 可能会有异常
void g() noexcept {
f(); // 合法,即使f可能有异常
throw 42; // 合法, 调用std::terminate
}
C++11 Library Features
std::move
std::move
代表对象在传入的时候有可能会有资源传输。当使用调用过std::move
的对象的时候应该小心,因为他的某些值可能已经处于一个未知的状态。
std::move
定义:
template <typename T>
typename remove_reference<T>::type&&
move(T&& arg) {
// remove_reference 作用是去除T类型的引用
return static_cast<typename remove_reference<T>::type&&>(arg);
}
std::unique_ptr
中的使用:
std::unique_ptr<int> p1 {new int{0}}; // in practice, use std::make_unique
std::unique_ptr<int> p2 = p1; // error -- cannot copy unique pointers
std::unique_ptr<int> p3 = std::move(p1); // move `p1` into `p3`
// now unsafe to dereference object held by `p1`
std::forward
返回的结果的值和传递给它的参数的值相同,同时维护了参数的值种类cv-限定符
。对万能模板引用代码很有用。一般与 forwarding references
连用。
std::forward
的定义
template <typename T> // 当传入是左值时,调用此函数,但传入的左值可能是具名右值,
// 所以返回结果要看传入的T的具体类型而定。
T&& forward(typename remove_reference<T>::type& arg) noexcept {
return static_cast<T&&>(arg);
}
template <typename T> // 当传入的是纯右值时,调用此函数
T&& forward(typename remove_reference<T>::type&& arg) noexcept {
static_assert(!is_lvalue_reference<T>::value, // 如果T的类型是左值引用,报错
"can not forward an rvalue as an lvalue");
return static_cast<T&&>(arg);
}
例子: wrapper
函数仅用传来的
struct A {
A() = default;
A(const A& o) { std::cout << "copied" << std::endl; }
A(A&& o) { std::cout << "moved" << std::endl; }
};
template <typename T>
A wrapper(T&& arg) {
return A{std::forward<T>(arg)};
}
wrapper(A{}); // moved
A a;
wrapper(a); // copied
wrapper(std::move(a)); // moved
注意:std::forward
作用主要有两个,一是将具名右值在传递时继续按照右值传递下去,二是在万能模板引用中自动推导左右值。
See also: forwarding references
, rvalue references
.
std::thread
std::thread
库提供了一种标准方式去控制线程,如生成线程或杀死线程。下面的例子,生成多个线程去做不同的计算然后程序等待所有线程结束。
void foo(bool clause) { /* do something... */ }
std::vector<std::thread> threadsVector;
threadsVector.emplace_back([]() {
// Lambda function that will be invoked
});
threadsVector.emplace_back(foo, true); // thread will run foo(true)
for (auto& thread : threadsVector) {
thread.join(); // Wait for threads to finish
}
std::to_string
转换一个数学类型到字符串
std::to_string(1.2); // == "1.2"
std::to_string(123); // == "123"
Type traits
static_assert(std::is_integral<int>::value);
static_assert(std::is_same<int, int>::value);
static_assert(std::is_same<std::conditional<true, int, double>::type, int>::value);
static_assert(is_lvalue_reference<_Tp>::value);
static_assert(is_rvalue_reference<_Tp>::value);
Smart pointers
std::unique_ptr
, std::shared_ptr
, std::weak_ptr
。 std::auto_ptr
在
std::unique_ptr
在管理其指向的堆内存对象时,要求对象不可复制,仅可移动。
std::make_X
来构造智能指针。
查看 std::make_unique
and std::make_shared
.
std::unique_ptr<Foo> p1 { new Foo{} }; // `p1` owns `Foo`
if (p1) {
p1->bar();
}
{
std::unique_ptr<Foo> p2 {std::move(p1)}; // Now `p2` owns `Foo`
f(*p2);
p1 = std::move(p2); // Ownership returns to `p1` -- `p2` gets destroyed
}
if (p1) {
p1->bar();
}
// `Foo` instance is destroyed when `p1` goes out of scope
std::shared_ptr
是一个智能指针,它管理跨多个所有者共享的资源。共享指针持有一个控制块,该控制块具有一些组件,如托管对象和引用计数器。 所有控制块访问都是线程安全的,但是操作托管对象本身是非线程安全的。
// p1在复制给参数时是线程安全的,保证计数正确,但是在操作对象时不是线程安全的
void foo(std::shared_ptr<T> t) {
// Do something with `t`...
}
void bar(std::shared_ptr<T> t) {
// Do something with `t`...
}
void baz(std::shared_ptr<T> t) {
// Do something with `t`...
}
std::shared_ptr<T> p1 {new T{}};
// Perhaps these take place in another threads?
foo(p1);
bar(p1);
baz(p1);
std::chrono
chrono
库包含一组实用函数和类型,用于处理持续时间、时钟和时间点。下面用例是程序运行时间测试代码
std::chrono::time_point<std::chrono::steady_clock> start, end;
start = std::chrono::steady_clock::now();
// Some computations...
end = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed_seconds = end - start;
double t = elapsed_seconds.count(); // t number of seconds, represented as a `double`
Tuples
元组是用来存储不同类型值得集合。解析元组用std::tie
或std::get
// `playerProfile` has type `std::tuple<int, const char*, const char*>`.
auto playerProfile = std::make_tuple(51, "Frans Nielsen", "NYI");
std::get<0>(playerProfile); // 51
std::get<1>(playerProfile); // "Frans Nielsen"
std::get<2>(playerProfile); // "NYI"
std::tie
返回一个左值引用的std::pair
和std::tuple
很有用,用std::ignore
占位符忽略某个值。
// With tuples...
std::string playerName;
std::tie(std::ignore, playerName, std::ignore) = std::make_tuple(91, "John Tavares", "NYI");
// With pairs...
std::string yes, no;
std::tie(yes, no) = std::make_pair("yes", "no");
std::array
std::array
是一个基于
std::array<int, 3> a = {2, 1, 3};
std::sort(a.begin(), a.end()); // a == { 1, 2, 3 }
for (int& x : a) x *= 2; // a == { 2, 4, 6 }
Unordered containers
这些容器操作如插入,删除,查找的平均时间复杂度是常数时间级别。容器为了使操作能达到常数级别的时间复杂度,将元素的存储在桶中,而不是以往的红黑树,所以牺牲了排序的功能。有四种不排序容器:
unordered_set
unordered_multiset
unordered_map
unordered_multimap
std::make_shared
推荐使用std::make_shared
来构建std::shared_ptr
的实例的原因如下:
- 避免使用
new
运算符 Prevents code repetition when specifying the underlying type the pointer shall hold.( 译者没懂要表达什么意思,防止代码重复,但我没感觉到有这个意义) - It provides exception-safety. Suppose we were calling a function
foo
like so:
foo(std::shared_ptr<T>{new T{}}, function_that_throws(), std::shared_ptr<T>{new T{}});
The compiler is free to call new T{}
, then function_that_throws()
, and so on… Since we have allocated data on the heap in the first construction of a T
, we have introduced a leak here. With std::make_shared
, we are given exception-safety:
foo(std::make_shared<T>(), function_that_throws(), std::make_shared<T>());
上述是原文,个人认为描述的跟我理解的有出入,以下是个人理解。
保证异常安全。假设我们现在在调用foo
函数:
foo(std::shared_ptr<T>{new T{}}, function_that_throws());
编译器在调用函数前会先将参数处理完成。需要处理的参数new T{}
,std::share_ptr<T>
构造,function_that_throws
函数。但是编译器的处理顺序不是确定的,当执行顺序为
foo(std::make_shared<T>(), function_that_throws());
std::shared_ptr{ new T{} }
再调用时,除了给T
分配内存,还要再给shared_ptr
中的控制块分配内存,一共分配了两次,而使用std::make_shared
来构造时,会一次性分配好两者的内存。只会进行一次内存动态分配。
std::ref
std::ref(val)
通过生成一个std::reference_wrapper
来保证参数传递时按照引用传递。通常用来显式指定传递参数为引用传递,或指定容器中保存的类型是某种类型的引用。
// create a container to store reference of objects.
auto val = 99;
auto _ref = std::ref(val);
_ref++;
auto _cref = std::cref(val);
//_cref++; does not compile
std::vector<std::reference_wrapper<int>>vec; // vector<int&>vec does not compile
vec.push_back(_ref); // vec.push_back(&i) does not compile
cout << val << endl; // prints 100
cout << vec[0] << endl; // prints 100
cout << _cref; // prints 100
Memory model
atomic loads/stores
、compare-and-swap
、atomic flags
、promises
、futures
、locks
和condition variables
。
查看相关部分
std::async
创建一个后台异步线程来执行任务,返回一个std::future
对象来存储函数调用返回的结果。
可以手动的选择异步线程的执行方式,将下面执行方式置为std::async
的第一个参数即可:
std::launch::async
在后台线程中执行可调用对象。当返回的future 失效前会强制执行完成可调用对象,即不调用future.get 也会保证任务的执行。std::launch::deferred
仅当调用future.get 时才会执行任务。std::launch::async | std::launch::deferred
如果创建async 时不指定launch policy ,他会默认在二者中选一种方式进行执行。
int foo() {
/* Do something here, then return the result. */
return 1000;
}
auto handle = std::async(std::launch::async, foo); // create an async task
auto result = handle.get(); // wait for the result
std::begin/end
一般而言,std::begin
std::end
template <typename T>
int CountTwos(const T& container) {
return std::count_if(std::begin(container), std::end(container), [](int item) {
return item == 2;
});
}
std::vector<int> vec = {2, 2, 43, 435, 4543, 534};
int arr[8] = {2, 43, 45, 435, 32, 32, 32, 32};
auto a = CountTwos(vec); // 2
auto b = CountTwos(arr); // 1
Acknowledgements
- cppreference
- 其中有很好的例子,特性和解释。 - C++ Rvalue References Explained
- 一系列非常好的右值引用,完美转发, 移动语义文章。 - clang 和 gcc 的标准支持标准页面。
- Compiler explorer 一个由源码转到汇编代码的网页,
( 译者:无敌好用) 。 - Scott Meyers’ Effective Modern C++
- 特别推荐的书籍 - Jason Turner’s C++ Weekly
- C++ 相关视频 - What can I do with a moved-from object? 被以右值引用调用过的对象还能用来做什么
- What are some uses of decltype(auto)?
decltype(auto) 的用途都有啥 - 还有很多帖子,但是我忘记了…