本节是对 Bjarne Stroustrup 的论文《Thriving in a Crowded and Changing World: C++ 2006–2020》的总结。
Circle* pc = dynamic_cast(p);
Circle& rc = dynamic_cast(r);
C++98 中最重要的技术之一
思想
RAII 加上智能指针消除了对垃圾收集的需求
该算法在三个维度上都是通用的
算法没有用到任何面向对象的方法。只依赖模板的泛型编程,有时也被称为编译期多态
atomic x;
mutex mutex_x;
atomic<bool> init_x; // 初始为 false
int x;
if (!init_x) {
lock_guard<mutex> lck(mutex_x);
if (!init_x) x = 42;
init_x = true ;
} // 在此隐式释放 mutex_x(RAII)
// ... 使用 x ...
template<typename T>
class stack {
std::atomic<node<T>*> head;
public:
void push(const T& data)
{
node<T>* new_node = new node<T>(data);
new_node->next = head.load(std::memory_order_relaxed);
while(!head.compare_exchange_weak(new_node->next, new_node,
std::memory_order_release, std::memory_order_relaxed)) ;
}
// ...
};
lock(lck1,lck2,lck3);
decltype(r) r2 = r;
template auto vector::begin() -> iterator { /* ... */ }
auto f() -> int;
Channel auto y = flopscomps(x,3); // y 可以当做 Channel 使用
for (auto i : {1,2,3,5,8}) sum+=i;
Matrix operator+(const Matrix&, const Matrix&);
Matrix mx = m1+m2+m3; // 不需要临时变量
template <class T, class A1>
std::shared_ptr<T> factory(A1&& a1)
{
return std::shared_ptr<T>(new T(std::forward<A1>(a1)));
}
int f(vector<int>);
int i = f({1,2,3}); // 函数参数
struct X {
vector<int> v;
int a[];
X() : v{1,2}, a{3,4} {} // 成员初始化器
X(int);
// ...
}
vector<int>* p = new vector<int>{1,2,3,4}; // new 表达式
X x {}; // 默认初始化
template<typename T> int foo(T);
int z = foo(X{1}); // 显式构造
constexpr Imaginary operator""i(long double x) { return Imaginary(x); }
[[noreturn]] void forever() {...}
struct Less_than {
int& i;
Less_than(int& ii) :i(ii) {} // 绑定到 i
bool operator()(int x) { return x<i; } // 跟参数比较
}
void test()
{
string s;
// ... 为 s 计算一个合适的值 ...
w.foo_callback([&s](int i){ do_foo(i,s); });
w.bar_callback([=s](double d){ return do_bar(d,s); });
}
template
示例
typedef double (*analysis_fp)(const vector&);
using analysis_fp = double (*)(const vector&);
类型和模板别名是某些最有效的零开销抽象及模块化技巧的关键
比较好用的模板示例
template<InputTransport Transport, MesssageDecoder MessageAdapter>
class InputChannel {
public:
template<typename... TransportArgs>
InputChannel(TransportArgs&&... transportArgs)
: _transport {forward<TransportArgs>(transportArgs)... }
{}
// ...
Transport _transport;
}
auto SVD(const Matrix& A) -> tuple<Matrix, Vector, Matrix>
{
Matrix U, V;
Vector S;
// ...
return {U,S,V};
};
void use()
{
Matrix A;
// ...
auto [U,S,V] = SVD(A); // 使用元组形式和结构化绑定
}
void do_something(int n) noexcept {...}
[p = move(ptr)] {/* ... */};
将值移入 lambda 表达式x = get(t)
;auto a = 1_234_567_s; // 1234567 秒
auto a = 1'234'567; // 1234567(整数)
auto b = 1'234'567s; // 1234567 秒
template<typename T>
constexpr T pi = T(3.1415926535897932385);
template<typename T>
T circular_area(T r)
{
return pi<T> * r * r;
}
C++20 标准库提供了一组定义为变量模板的数学常数
template<typename T> constexpr T pi_v = unspecified;
constexpr double pi = pi_v<double>;
C++11 引入了从 lambda 表达式的 return 语句来推导其返回类型的特性。C++14 将该特性扩展到了函数
template<typename T>
auto size(const T& a) { return a.size(); }
auto get_size = [](auto& m){ return m.size(); }
允许使用局部变量和 for 循环
constexpr int min(std::initializer_list<int> xs)
{
int low = std::numeric_limits<int>::max();
for (int x : xs)
if (x < low)
low = x;
return low;
}
constexpr int m = min({1,3,2,4});
C++17 有大约 21 个新的语言特性:
shared_lock lck {m}; // 不需要显式写出锁类型
简化写法和消除剩余变量
template<typename T, typename U>
void print(vector<pair<T,U>>& v)
{
for (auto [x,y] : v)
cout << '{' << x << ' ' << y << "}\n";
}
auto [x,y,z] = f(); // 调用语法,引入别名
optional<int> var1 = 7;
variant<int,string> var2 = 7;
any var3 = 7;
auto x1 = *var1 ; // 对 optional 解引用
auto x2 = get<int>(var2); // 像访问 tuple 一样访问 variant
auto x3 = any_cast<int>(var3); // 转换 any
可选类型可以用 union 表示,没有运行期开销
overloaded 实现
using var_t = std::variant<int, long, double, std::string>; // variant 类型
// 简单访问的样板:
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
void use()
{
std::vector<var_t> vec = {10, 20L, 30.40, "hello"};
for (auto& var : vec) {
std::visit (overloaded {
[](auto arg) { cout << arg << '\n'; }, // 处理整数类型
[](double arg) { cout << "double : " << arg << '\n'; },
[](const std::string& arg) { cout << "\"" << arg << "\"\n"; },
}, var);
}
}
sort(par_unseq, begin(v), end(v)); // 考虑并行和向量化
sort(v);
sort(par_unseq, v); // 使用并行和向量化来对 v 进行排序
template<typename T>
concept execution_policy = std::is_execution_policy<T>::value;
void sort(execution_policy auto&& ex, std::random_access_range auto& r)
{
sort(ex, begin(r), end(r)); // 使用执行策略 ex 来排序
}
void do_something(const string& name)
{
path p {name}; // name 可能是俄语或阿拉伯语
// name 可能使用 Windows 或 Linux 文件写法
try {
if (exists(p)) {
if (is_regular_file(p))
cout << p << " regular file, size: " << file_size(p) << '\n';
else if (is_directory(p)) {
cout << p << " directory, containing:\n";
for (auto& x : directory_iterator(p))
cout << " " << x.path() << '\n';
}
else
cout << p << " exists\n";
}
else
cout << p << " does not exist\n";
}
catch (const filesystem_error& ex) {
cerr << ex.what() << '\n';
throw;
}
// ... 使用 p ...
}
if (auto p = f(y); p->m>0) {
// ...
}
export module map_printer; // 定义一个模块
import iostream; // 使用 iostream
import containers; // 使用我自己的 containers
using namespace std;
export // 让 print_map() 对 map_printer 的用户可用
template<Sequence S>
requires Printable<Key_type<S>> && Printable<Value_type<S>>
void print_map(const S& m) {
for (const auto& [key,val] : m) // 分离“键”和“值”
cout << key << " -> " << val << '\n';
}
export module map_printer; // 定义一个模块
import // 使用 iostream 头文件
import "containers" // 使用我自己的 containers 头文件
using namespace std;
export // 让 print_map() 对 map_printer 的用户可用
template<Sequence S>
requires Printable<Key_type<S>> && Printable<Value_type<S>>
void print_map(const S& m) {
for (const auto& [key,val] : m) // 分离“键”和“值”
cout << key << " -> " << val << '\n';
}
演化工作组引入了关键字 co_return、co_yield 和 co_await,用于协程中的三个关键操作
TS 协程的一个重要且可能致命的问题是,它依赖于自由存储区(动态内存、堆)上的分配
协程示例
generator<int> fibonacci() // 生成 0,1,1,2,3,5,8,13 ...
{
int a = 0; // 初值
int b = 1;
while (true) {
int next = a+b;
co_yield a; // 返回下一个斐波那契数
a = b; // 更新值
b = next;
}
}
int main()
{
for (auto v : fibonacci())
cout << v << '\n';
}
sort(vs);
vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto even = [](int i){ return i%2 == 0; }
for (int i : vec | view::filter(even)
| view::transform( [](int i) { return i*i; } )
| view::take(5))
cout << i << '\n'; // 打印前 5 个偶整数的平方
和旧的时间工具一起放在 中
表达时间点(time_point)
constexpr auto tp = 2016y/May/29d + 7h + 30min + 6s + 153ms;
cout << tp << '\n'; // 2016-05-29 07:30:06.153
static_assert(2016y/May/29==Thursday); // 编译期检查
默认情况下,时区是 UTC(又称 Unix 时间),但转换为不同的时区很容易
zoned_time zt = {"Asia/Tokyo", tp};
cout << zt << '\n'; // 2016-05-29 16:30:06.153 JST
cout << format("The string '{0}' has {1} characters",s,s.size());
cout << format("The string '{1}' has {0} characters",s.size(),s);
string s1 = format("{}", birthday);
string s2 = format("{0:>15%Y-%m-%d}", birthday);
15 意味着使用 15 个字符和右对齐文本
std::format(std::locale{"fi_FI"}, "{}", zt);
越界访问,有时也称为缓冲区溢出,从 C 的时代以来就一直是一个严重的问题
span 类模板
void f(span<int> a) // span 包含一根指针和一条大小信息
{
for (int& x : a)
x = 7; // 可以
}
如果有必要的话,可以显式地指定一个大小(比如操作一个子范围)
int x = 100;
int a[100];
f(a); // 模板参数推导:f(span{a, 100})
f({a,x/2}); // 可以:a 的前半部分
f({a,x+1}); // 灾难
void some_fct()
{
thread t1;
jthread t2;
// ...
}