C++11
语言特性
移动语义
移动对象意味着将其管理的某些资源的所有权转移给另一个对象。
就相当于将某个对象的内存移交给另一个对象。中间没有发生复制。
移动带来的好处就是性能上的优化,例如:
std::vector<std::string> getStrings()
{
std::vector<std::string> strs;
// do something
return strs;
}
在C++11以前,返回一个vector
会产生复制,而旧的vector
又会被释放,造成了性能上的浪费。
而在C++11以后,则是会把旧对象的内存移交给新对象,不产生复制。这里不用显式调用std::move
,系统会自动移动。
移动还使诸如std::unique_ptrs
(智能指针)之类的不可复制类型可以在语言级别上保证一次只管理一个资源的实例,同时能够在两者之间传输一个实例范围。
自定义对象要想实现移动语义,需要有移动构造函数。
class A{
public:
A(A&& a); // 移动构造函数声明
}
右值引用
C++11 引入了一个新的引用,称为右值引用。使用语法T&&
创建对T
的右值引用,这个T
可以是C++自带类型,也可以是自定义类型,但是必须是具体的类型,不能是模板类型。
右值引用仅绑定到右值。
int x = 0; // `x` 是 `int` 类型的左值
int& xl = x; // `xl` 是 `int&` 类型的左值
int&& xr = x; // 编译错误 -- `x` 是左值 无法绑定
int&& xr2 = 0; // `xr2` 是 `int&&` 类型的左值 -- 绑定到了临时的右值:`0`
void f(int& x) {}
void f(int&& x) {}
f(x); // 调用 f(int&)
f(xl); // 调用 f(int&)
f(3); // 调用 f(int&&)
f(std::move(x)) // 调用 f(int&&)
f(xr2); // 调用 f(int&)
f(std::move(xr2)) // 调用 f(int&& x)
转发引用
转发引用使用语法T&&
创建,其中T
是模板类型参数,或使用auto&&
。
这实现了完美的转发:在保持其值类别的同时传递参数的能力(例如,左值保留为左值,临时值作为右值转发)。
转发引用允许引用绑定到左值或右值,具体取决于类型。转发引用遵循引用折叠规则:
T& &
转发T&
T& &&
转发T&
T&& &
转发T&
T&& &&
转发T&&
左值和右值的自动类型推导:
int x = 0; // `x` 是 `int` 类型的左值
auto&& al = x; // `al` 是 `int&` 类型的左值 -- 绑定到左值: `x`
auto&& ar = 0; // `ar` 是 `int&&` 类型的左值 -- 绑定到临时右值 `0`
带有左值和右值的模板类型参数推导:
// C++11以后可用
template <typename T>
void f(T&& t) {
// ...
}
// C++14以后可用,与上面等价
void f(auto&& t) {
// ...
}
int x = 0;
f(0); // T 是 int, 推导为 f(int &&) => f(int&&)
f(x); // T 是 int&, 推导为 f(int& &&) => f(int&)
int& y = x;
f(y); // T 是 int&, 推导为 f(int& &&) => f(int&)
int&& z = 0; // 注: `z` 是 `int&&` 类型的左值
f(z); // T 是 int&, 推导为 f(int& &&) => f(int&)
f(std::move(z)); // T 是 int, 推导为 f(int &&) => f(int&&)
如果参数为左值,则推导类型为引用,如果参数为右值,则推导类型为原始类型。
可变参数模板
...
语法创建一个参数包或展开一个参数包。模板参数包是接受零个或多个模板参数(非类型、类型或模板)的模板参数。具有至少一个参数包的模板称为可变参数模板。
使用sizeof...
运算符可以获取参数包里模板参数的数量
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
初始化列表
使用“支撑列表”语法创建的轻量级数组元素容器。例如,{ 1, 2, 3 }
创建一个整数序列,其类型为std::initializer_list<int>
。可用作将对象向量传递给函数的替代方法。
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
静态断言
在编译时评估的断言。
constexpr int x = 0;
constexpr int y = 1;
static_assert(x == y, "x != y"); // 后面的信息会在编译器里输出
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; // 错误 -- `l` 推导为 int, `m` 则是 bool
auto o; // 错误 -- `o` 需要初始化变量
对可读性非常有用,尤其是对于复杂类型:
std::vector<int> v = {1, 2, 3};
std::vector<int>::const_iterator cit = v.cbegin();
// 可以简便的写为
auto cit = v.cbegin();
函数还可以使用auto
推断返回类型。在 C++11 中,必须显式指定返回类型,或者使用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
的声明类型(参见decltype
部分)。例如,如果 x 是整数且 y 是双精度数,则decltype(x + y)
是双精度数。
因此,上述函数将根据表达式x + y
产生的类型来推断类型。请注意,尾随返回类型可以访问其参数,并且在适当的时候这样做。
decltype类型声明
decltype
是一个运算符,它返回传递给它的表达式的声明类型。如果cv
限定符和引用是表达式的一部分,则它们将被保留。decltype
的示例:
int a = 1; // `a` 被声明为 `int`
decltype(a) b = a; // `decltype(a)` 是 `int`
const int& c = a; // `c` 被声明为 `const int&`
decltype(c) d = a; // `decltype(c)` 是 `const int&`
decltype(123) e = 123; // `decltype(123)` 是 `int`
int&& f = 1; // `f` 被声明为 `int&&`
decltype(f) g = 1; // `decltype(f) 是 `int&&`
decltype((a)) h = g; // `decltype((a))` 是 int&
Lambda 表达式
lambda 是一个未命名的函数对象,能够捕获范围内的变量。它的特点:一个捕获列表;带有可选尾随返回类型的可选参数集;和一个函数主体。
[/*捕获列表*/](Type v) /*mutable*/ ->RreturnType
{
// 函数主题
}
捕获列表示例:
[]
,什么都不捕获[=]
,按值捕获范围内的局部对象(局部变量、参数)。[&]
,通过引用捕获范围内的局部对象(局部变量、参数)。[this]
,按引用捕获this
指针。[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& 引用 `x`
默认情况下,无法在 lambda 内部修改按值捕获,因为编译器生成的方法被标记为const
。 mutable
关键字允许修改捕获的变量。关键字放在参数列表之后(即使它为空也必须存在)。
int x = 1;
auto f1 = [&x] { x = 2; }; // 正确:x是对原对象的引用
auto f2 = [x] { x = 2; }; // 错误:按值捕获默认是const
// vs.
auto f3 = [x]() mutable { x = 2; }; // 正确:加上mutable后就可以修改按值捕获的对象
类型别名
语义上类似于使用typedef
,但是使用using
的类型别名更易于阅读并且与模板兼容。
template <typename T>
using Vec = std::vector<T>;
Vec<int> v; // std::vector<int>
using String = std::string;
String s {"foo"};
nullptr
C++11 引入了一种新的空指针类型,旨在替换 C 的 NULL 宏。nullptr
本身是std::nullptr_t
类型,可以隐式转换为指针类型,并且与 NULL 不同,不能转换为除bool
之外的整数类型。
void foo(int);
void foo(char*);
foo(NULL); // 错误 -- 歧义
foo(nullptr); // 调用foo(char*)
强类型枚举
类型安全的枚举解决了 C 样式枚举的各种问题,包括:隐式转换、无法指定基础类型、作用域污染。
// 将基础类型指定为`unsigned int`
enum class Color : unsigned int { Red = 0xff0000, Green = 0xff00, Blue = 0xff };
// `Alert` 中的 `Red`/`Green` 与 `Color` 不冲突
enum class Alert : bool { Red, Green };
Color c = Color::Red;
属性
属性为 __attribute__(...)
、__declspec
等提供通用语法。
// `noreturn` 属性表示 `f` 不返回。
[[ 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; // 错误 -- constexpr 变量 `y` 必须由常量表达式初始化
类中的常量表达式
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);
委托构造函数
构造函数现在可以使用初始化列表调用同一类中的其他构造函数。
struct Foo {
int foo;
Foo(int foo) : foo{foo} {}
Foo() : Foo(0) {}
};
Foo foo;
foo.foo; // == 0
用户自定义字面量
用户定义的字面量允许您扩展语言并添加自己的语法。要创建一个字面量,请定义一个T operator "" X(...) { ... }
函数,该函数名称为 X返回一个类型T
。
请注意,此函数的名称定义了文字的名称。任何不以下划线开头的文字名称都是保留的,不会被调用。根据调用文字的类型,用户定义的文字函数应接受哪些参数有一些规则。
将摄氏度转换为华氏度:
// 整数文字需要“unsigned long long”参数。
long long operator "" _celsius(unsigned long long tempCelsius) {
return std::llround(tempCelsius * 1.8 + 32);
}
24_celsius; // == 75
字符串到整数的转换:
// `const char*` 和 `std::size_t` 需要作为参数。
int operator "" _int(const char* str, std::size_t) {
return std::stoi(str);
}
"123"_int; // == 123, with type `int`
显式virtual覆盖
指定一个虚函数覆盖另一个虚函数。如果虚函数没有覆盖父虚函数,则引发编译器错误。
struct A {
virtual void foo();
void bar();
};
struct B : A {
void foo() override; // 正确 -- B::foo 覆盖 A::foo
void bar() override; // 错误 -- A::bar 不是虚函数
void baz() override; // 错误 -- B::baz 没有覆盖任何函数
};
最终说明符
指定不能在派生类中重写虚函数或不能继承某个类。
struct A {
virtual void foo();
};
struct B : A {
virtual void foo() final;
};
struct C : B {
virtual void foo(); // 错误 -- 'foo' 的声明覆盖了 'final' 函数
};
类不能被继承
struct A final {};
struct B : A {}; // 错误 -- 基类A被声明为final
默认函数
一种更优雅、更有效的方式来提供函数的默认实现,例如构造函数。
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 {
// 调用 B::B
C() = default;
};
C c; // c.x == 1
删除函数
提供删除的函数实现的更优雅、更有效的方法。用于防止对象上的复制。
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; // 错误 -- 调用已删除的复制构造函数
y = x; // 错误 -- operator= 已经被删除
基于范围的 for 循环
用于迭代容器元素的语法糖。
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 }
移动语义的特殊成员函数:移动构造函数
复制时调用复制构造函数和复制赋值运算符,并且随着 C++11 引入移动语义,现在有一个移动构造函数和移动赋值运算符用于移动。
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{}); // 从右值临时移动构造
A a2 = std::move(a1); // 使用 std::move 移动构造
A a3 = A{};
a2 = std::move(a3); // 使用 std::move 移动赋值
a1 = f(A{}); // 从右值临时移动赋值
转换构造函数
转换构造函数会将花括号列表语法的值转换为构造函数参数。
struct A {
A(int) {}
A(int, int) {}
A(int, int, int) {}
};
A a {0, 0}; // 调用 A::A(int, int)
A b(0, 0); // 调用 A::A(int, int)
A c = {0, 0}; // 调用 A::A(int, int)
A d {0, 0, 0}; // 调用 A::A(int, int, int)
请注意,花括号列表语法不允许缩小:
struct A {
A(int) {}
};
A a(1.1); // OK
A b {1.1}; // 缩小从 double 到 int 的转换时出错
请注意,如果构造函数接受 std::initializer_list,则会改为调用它:
struct A {
A(int) {}
A(int, int) {}
A(int, int, int) {}
A(std::initializer_list<int>) {}
};
A a {0, 0}; // 调用 A::A(std::initializer_list<int>)
A b(0, 0); // 调用 A::A(int, int)
A c = {0, 0}; // 调用 A::A(std::initializer_list<int>)
A d {0, 0, 0}; // 调用 A::A(std::initializer_list<int>)
显式转换函数
现在可以使用显式说明符使转换函数显式化。
struct A {
operator bool() const { return true; }
};
struct B {
explicit operator bool() const { return true; }
};
A a;
if (a); // OK 调用 A::operator bool()
bool ba = a; // OK 复制初始化选择 A::operator bool()
B b;
if (b); // OK 调用 B::operator bool()
bool bb = b; // 错误 复制初始化不考虑 B::operator bool()
内联命名空间
内联命名空间的所有成员都被视为其父命名空间的一部分,从而允许函数的专门化并简化版本控制过程。这是一个传递属性,如果 A 包含 B,而 B 又包含 C,并且 B 和 C 都是内联命名空间,则 C 的成员可以像在 A 上一样使用。
namespace Program {
namespace Version1 {
int getVersion() { return 1; }
bool isFirstVersion() { return true; }
}
inline namespace Version2 {
int getVersion() { return 2; }
}
}
int version {Program::getVersion()}; // 使用来自 Version2 的 getVersion()
int oldVersion {Program::Version1::getVersion()}; // 使用来自 Version1 的 getVersion()
bool firstVersion {Program::isFirstVersion()}; // 添加 Version2 时不编译
非静态数据成员初始化器
允许在声明它们的地方初始化非静态数据成员,从而可能清除默认初始化的构造函数。
// C++11 之前的默认初始化
class Human {
Human() : age{0} {}
private:
unsigned age;
};
// C++11 上的默认初始化
class Human {
private:
unsigned age {0};
};
尖括号
C++11 现在能够推断出一系列直角括号何时用作运算符或typedef
的结束语句,而无需添加空格。
typedef std::map<int, std::map <int, std::map <int, int> > > cpp98LongTypedef;
typedef std::map<int, std::map <int, std::map <int, int>>> cpp11LongTypedef;
引用限定的成员函数
现在可以根据*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(); // 调用 `Bar getBar() &`
const Foo foo2{};
Bar bar2 = foo2.getBar(); // 调用 `Bar Foo::getBar() const&`
Foo{}.getBar(); // 调用 `Bar Foo::getBar() &&`
std::move(foo).getBar(); // 调用 `Bar Foo::getBar() &&`
std::move(foo2).getBar(); // 调用 `Bar Foo::getBar() const&&`
后置返回类型
C++11 允许函数和 lambda 使用另一种语法来指定它们的返回类型。
int f() {
return 123;
}
// vs.
auto f() -> int {
return 123;
}
auto g = []() -> int {
return 123;
};
当某些返回类型无法解析时,此功能特别有用:
// 注意:这里还没编译
template <typename T, typename U>
decltype(a + b) add(T a, U b) {
return a + b;
}
// 后置返回类型
template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}
在C++14, decltype(auto) (C++14)
可以替代该写法
noexcept说明符
noexcept
说明符指定函数是否可以抛出异常。它是throw()
的改进版本。
void func1() noexcept; // 不抛异常
void func2() noexcept(true); // 不抛异常
void func3() throw(); // 不抛异常
void func4() noexcept(false); // 可能抛异常
允许非抛出函数调用潜在抛出函数。每当抛出异常并且对处理程序的搜索遇到非抛出函数的最外层块时,就会调用函数std::terminate
。
char32_t和char16_t
提供用于表示 UTF-8 字符串的标准类型。
char32_t utf8_str[] = U"\u0123";
char16_t utf8_str[] = u"\u0123";
原始字符串字面量
C++11 引入了一种将字符串文字声明为“原始字符串文字”的新方法。从转义序列发出的字符(制表符、换行符、单反斜杠等)可以在保留格式的同时输入原始字符。例如,这对于编写可能包含大量引号或特殊格式的文学文本很有用。这可以使您的字符串文字更易于阅读和维护。
使用以下语法声明原始字符串文字:
R"delimiter(raw_characters)delimiter"
这里
delimiter
是可选的字符序列,由除括号、反斜杠和空格之外的任何源字符组成。raw_characters
是任何原始字符序列;不得包含结束序列")delimiter"
。
// msg1和msg2是等价的
const char* msg1 = "\nHello,\n\tworld!\n";
const char* msg2 = R"(
Hello,
world!
)";
标准库
std::move
std::move
,可以将一个左值转化为右值表示传递给它的对象可能会转移其资源。
使用已移出的对象应小心使用,因为它们可能处于未指定状态。Stackoverflow std::move
std::move
的定义(执行移动只不过是转换为右值引用):
template <typename T>
typename remove_reference<T>::type&& move(T&& arg) {
return static_cast<typename remove_reference<T>::type&&>(arg);
}
传输std::unique_ptr
:
std::unique_ptr<int> p1 {new int{0}}; // 在实践中,使用std::make_unique
std::unique_ptr<int> p2 = p1; // 错误 -- 无法复制unique_ptr
std::unique_ptr<int> p3 = std::move(p1); // 移动 `p1` 给 `p3`
// 现在取消引用由`p1`持有的对象是不安全的
std::forward
返回传递给它的参数,同时保持它们的值类别和 cv 限定符。对于通用代码和工厂很有用。与转发引用一起使用。
template <typename T>
T&& forward(typename remove_reference<T>::type& arg) {
return static_cast<T&&>(arg);
}
将其他 A 对象转发到新 A 对象的复制或移动构造函数的函数包装器示例:
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::thread
std::thread
库提供了一种控制线程的标准方法,例如生成和终止线程。
在下面的示例中,生成了多个线程来执行不同的计算,然后程序等待所有线程完成。
void foo(bool clause) { /* do something... */ }
std::vector<std::thread> threadsVector;
threadsVector.emplace_back([]() {
// 将被调用的 Lambda 函数
});
threadsVector.emplace_back(foo, true); // thread将会执行foo(true)
for (auto& thread : threadsVector) {
thread.join(); // 等待线程完成
}
std::to_string
将数字参数转换为std::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);
智能指针
C++11 引入了新的智能指针:std::unique_ptr
、std::shared_ptr
、std::weak_ptr
。
而std::auto_ptr
现在已被弃用,然后最终在 C++17 中被删除。
std::unique_ptr
是一个不可复制的、可移动的指针,它管理自己的堆分配内存。
注意:与使用构造函数相比,最好使用std::make_X
辅助函数。
请参阅std::make_unique
和std::make_shared
部分。
std::unique_ptr<Foo> p1 { new Foo{} }; // `p1` 拥有 `Foo`
if (p1) {
p1->bar();
}
{
std::unique_ptr<Foo> p2 {std::move(p1)}; // 现在`p2`拥有`Foo`
f(*p2);
p1 = std::move(p2); // 所有权返回到 `p1` - `p2` 被销毁
}
if (p1) {
p1->bar();
}
// 当 `p1` 超出范围时,`Foo` 实例被销毁
std::shared_ptr
是一个智能指针,用于管理在多个所有者之间共享的资源。共享指针包含一个控制块,该块具有一些组件,例如托管对象和引用计数器。所有控制块访问都是线程安全的,但是,操作托管对象本身不是线程安全的。
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{}};
// 也许这些发生在另一个线程中?
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();
// 一些操作...
end = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed_seconds = end - start;
double t = elapsed_seconds.count(); // t 秒数,表示为 `double`
Tuples
元组是一个固定大小的异构值集合。通过使用std::tie
或使用std::get
解包来访问std::tuple
的元素。
// `playerProfile` 是 `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
作为忽略值的占位符。在 C++17 中,应改为使用结构化绑定。
// tuples...
std::string playerName;
std::tie(std::ignore, playerName, std::ignore) = std::make_tuple(91, "John Tavares", "NYI");
// pairs...
std::string yes, no;
std::tie(yes, no) = std::make_pair("yes", "no");
std::array
std::array
是建立在 C 样式数组之上的容器。支持排序等常用容器操作。
就是对C数组的包裹,不可改变大小。
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_set
unordered_multiset
unordered_map
unordered_multimap
std::make_shared
std::make_shared
是创建std::shared_ptrs
实例的推荐方法,原因如下:
-
避免必须使用
new
运算符。 -
在指定指针应保持的基础类型时防止代码重复。
-
它提供异常安全。假设我们像这样调用函数
foo
:foo(std::shared_ptr<T>{new T{}}, function_that_throws(), std::shared_ptr<T>{new T{}});
编译器可以自由调用
new T{}
,然后调用function_that_throws()
,但是由于我们在第一次构造 T 时在堆上分配了数据,因此我们在这里导致了泄漏。使用std::make_shared
,我们获得了异常安全性:foo(std::make_shared<T>(), function_that_throws(), std::make_shared<T>());
-
防止必须进行两次分配。在调用
std::shared_ptr{ new T{} }
时,我们必须为 T 分配内存,然后在共享指针中我们必须为指针内的控制块分配内存。
std::ref
std::ref(val)
用于创建std::reference_wrapper
类型的对象,该对象包含val
的引用。在使用&
的常规引用传递未编译或&
由于类型推导而被删除的情况下使用。 std::cref
类似,但创建的引用包装器包含对val
的const
引用。
// 创建一个容器来存储对象的引用。
auto val = 99;
auto _ref = std::ref(val);
_ref++;
auto _cref = std::cref(val);
//_cref++; 无法通过编译
std::vector<std::reference_wrapper<int>>vec;
vec.push_back(_ref);
cout << val << endl; // 输出 100
cout << vec[0] << endl; // 输出 100
cout << _cref; // 输出 100
内存模型
C++11 为 C++ 引入了内存模型,这意味着库支持线程和原子操作。
其中一些操作包括(但不限于)原子加载/存储、比较和交换、原子标记、promises、futures、锁和条件变量。
std::async
std::async
以异步或延迟评估的方式运行给定的函数,然后返回一个std::future
,它保存该函数调用的结果。
第一个参数是策略,可以是:
std::launch::async | std::launch::deferred
执行异步执行还是惰性求值取决于实现。std::launch::async
在新线程上运行可调用对象。std::launch::deferred
对当前线程执行惰性求值。
int foo() {
/* 执行一些逻辑,然后返回 */
return 1000;
}
auto handle = std::async(std::launch::async, foo); // 创建一个异步任务
auto result = handle.get(); // 等待结果
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