0%
C++ 几种新增类型转换的区别 + 标准库模板函数
发表于:
四大关键字
static_cast大多数转换,包括左右值转换、父子类指针/引用之间的转换(不进行安全检查,但会修正指针偏移)。const_cast也能进行 volatile 属性的修改!!dynamic_cast父子类指针/引用之间的转换。其中子类转向基类相当于使用static_cast,没有运行时安全检查。而基类转向子类则有运行时安全检查,而且要求基类是多态类,否则无法编译。reinterpret_cast
标准库的共享指针转换模板
标准库中还有针对于 std::shared_ptr 的类型转换模板。转换后返回一个共享指针,但其包裹的类型被转换成了对应的类型:
std::static_pointer_caststd::const_pointer_caststd::dynamic_pointer_cast
注意:这种转换算作指针的复制,所以共享指针对应的控制块引用计数加一(如果指针非空)。
举例:
C++23 std::forward_like
发表于:
std::forward 需要我们提供一个模板参数,它能把同类型(不包括值类别)的参数转发出去。
std::forward_like 则有两个模板参数,第一个参数需要显式提供,第二个参数从实参中推导。它从模板参数中获得转发需要使用的引用类型,并将实参转发出去。这意味着实参和模板参数的类型可以不一致!
std::forward_like 非常适合转发子对象的场合,因为它能让子对象和本身的左右值类型相同。比如在显式对象参数中可以使用。
cppreference 协程例子理解
发表于:
更新于:
我的理解是:C++ 的协程是无栈的,这意味着协程只是计算任务,仅在运行时需要栈,在 suspend 之后就会保存状态并脱离栈。要想要跨线程转移计算任务(比如实现工作窃取池),只需要将离栈协程在另外一个线程上 resume 即可。
#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>
auto switch_to_new_thread(std::jthread& out)
{
struct awaitable
{
std::jthread* p_out;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h)
{
std::jthread& out = *p_out;
if (out.joinable())
throw std::runtime_error("Output jthread parameter not empty");
out = std::jthread([h] { h.resume(); });
// Potential undefined behavior: accessing potentially destroyed *this
// std::cout << "New thread ID: " << p_out->get_id() << '\n';
std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK
}
void await_resume() {}
};
return awaitable{&out};
}
struct task
{
struct promise_type
{
task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
task resuming_on_new_thread(std::jthread& out)
{
std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
co_await switch_to_new_thread(out);
// awaiter destroyed here
std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}
int main()
{
std::jthread out;
resuming_on_new_thread(out);
}
可能结果:
Coroutine started on thread: 139972277602112
New thread ID: 139972267284224
Coroutine resumed on thread: 139972267284224
enum class
发表于:
C 风格枚举和 C++ 新增的 enum class:
enum { ITEM_A1, ITEM_A2 };
enum B { ITEM_B1, ITEM_B2 };
// 从 C 沿用来的语法:
// 1. 底层类型默认为int,但也能手动指定
// 2. 枚举名裸露在外
// 区别:
// C 允许前向声明 enum
// C++ 允许枚举列表为空;允许在表示类型时省略 enum 关键字;虽然枚举名裸露但也能通过限定名访问
// C++ 新增 enum class
enum C { ITEM_C1, ITEM_C2 };
// 类型不再裸露,需要使用限定名访问
// 没有默认底层类型,但是可以手动指定
C++ enum class 禁止了隐式转换,但是用来表示 id 还是不太方便:
#include <type_traits>
enum A: int { ITEM_1 };
enum { ITEM_2 };
enum class B: int { ITEM_3 };
// 即便底层类型相同,实际类型也不同
static_assert(! std::is_same_v<int, A>);
static_assert(sizeof(int) == sizeof(A));
static_assert(sizeof(int) == sizeof(ITEM_2));
// 枚举不是聚合类
static_assert(! std::is_aggregate_v<A>);
static_assert(! std::is_aggregate_v<B>);
int main() {
[](int){}(ITEM_1);
[](int){}(ITEM_2);
int b3 = (int)B::ITEM_3;
// [](int){}(B::ITEM_3); // no conversion from B to int
// [](B){}(b3); // no conversion from int to B
[](B){}(B {b3});
// [](B){}( {b3} ); // 即便是花括号也不能推导,因为枚举不是类
// 强类型整数还是用 aggregate 做比较好:
// 虽然整数到枚举可以使用构造的方式描述,但枚举到整数只能强制转换
// 作为对比,aggregate 直接访问成员即可
}
explicit
发表于:
更新于:
一般认为 explicit 是用在单参数构造函数 上防止隐式类型转换的。但如果参数有多个,explicit 关键字也有其他的作用。
explicit 可以阻止 {} 推导
https://stackoverflow.com/a/39122237/
可以参考另一篇文章:{ } Syntax。
struct Foo { Foo(int, int) {} };
struct Bar { explicit Bar(int, int) {} };
Foo f1(1, 1); // ok
Foo f2 {1, 1}; // ok
Foo f3 = {1, 1}; // ok
Bar b1(1, 1); // ok
Bar b2 {1, 1}; // ok
Bar b3 = {1, 1}; // NOT OKAY
Bar many_bars[] = { {1, 1} }; // ERROR explicit阻止了 {1, 1} 被推导成 Bar{1, 1}
Bar many_bars2[] = { Bar{1, 1} }; // okay
阻止“去掉默认值是单参数构造函数”的隐式转换
如果将来某个构造函数除了第一个参数之外的其他参数有了默认值,则会引起和单参数构造函数一样的隐式转换问题。
Heterogeneous Lookup
发表于:
更新于:
https://www.cppstories.com/2021/heterogeneous-access-cpp20/
C++ 14 加入了对有序容器的异质查找
用户的工作量很小,对标准库中的类型,只需要加上第三个模板参数:std::less<>(它的默认参数是 void)。std::less<void> 类中申明了 is_transparent 类型,所以可以用于异质查找。
std::map<std::string, int> intMap {
{ "Hello Super Long String", 1 },
{ "Another Longish String", 2 },
{ "This cannot fall into SSO buffer", 3 }
};
std::cout << "Lookup in intMap with by const char*:\\n";
std::cout << intMap.contains("Hello Super Long String") << '\\n';
//
std::map<std::string, int, std::less<>> trIntMap {
{ "Hello Super Long String", 1 },
{ "Another Longish String", 2 },
{"This cannot fall into SSO buffer", 3 }
};
std::cout << "Lookup in trIntMap by const char*: \\n";
std::cout << trIntMap.contains("Hello Super Long String") << '\\n';
C++ 20 加入了对无序容器的异质查找
需要提供标注 is_transparent 的 hash 算子和等于算子。等于算子一般可以直接用 std::equal_to<>,但 hash 算子常常需要我们自己提供。
struct string_hash {
using is_transparent = void;
[[nodiscard]] size_t operator()(const char *txt) const {
// 对指针的hash只考虑了地址,所以要选用string_view的hash
return std::hash<std::string_view>{}(txt);
}
[[nodiscard]] size_t operator()(std::string_view txt) const {
return std::hash<std::string_view>{}(txt);
}
[[nodiscard]] size_t operator()(const std::string &txt) const {
// string和string_view的hash方式一样
// 但用string的特化会减少一次从string到string_view的构造
return std::hash<std::string>{}(txt);
}
};
std::unordered_map<std::string, int, string_hash, std::equal_to<>>
intMapTransparent {
{ "Hello Super Long String", 1 },
{ "Another Longish String", 2 },
{"This cannot fall into SSO buffer", 3 }
};
bool found = intMapTransparent.contains("Hello Super Long String");
std::cout << "Found: " << std::boolalpha << found << '\\n';
Hidden Friends
发表于:
下面的代码中,Bar 能够隐式转换成 Foo。想要重载 Foo 的等于运算符至少有三种方案:
- 重载全局的
==运算符 - 重载 Foo 中的 hidden friend,即
friend bool operator==(Foo const &a, Foo const &b) - 重载
bool operator==(Foo const &a) const
第一种方案会污染全局的名字空间,使得 Bar 和 Bar 之间也能通过转换两个参数进行比较;第三种方案允许第二个参数的隐式类型转换,但要求第一个参数必须是 Foo 类型;第二种方案只要求任一个参数为 Foo 类型,使得 ADL 能够参与找到这个函数,另一个参数则可以通过隐式类型转换来得到。
成员函数的劣势是由于查找方式和 this 指针的存在,函数失去了对称性。全局函数的劣势则是没有用上 ADL 的优势,hidden friends 在这种情况比较好。
Inheritance
发表于:
更新于:
| public | protected | private | |
|---|---|---|---|
| 共有继承 | public | protected | 不可见 |
| 私有继承 | private | private | 不可见 |
| 保护继承 | protected | protected | 不可见 |
可见继承属性就是对于来自基类的 public 和 protected 成员进行一个取最小权限的操作(定义权限 public > protected > private)。
struct 和 class 的数据定义没有区别。语法上区别是:struct 定义默认继承种类是 public,class 定义默认继承类型是 private,这和基类用 struct 还是 class 声明的无关。
继承多个类时,需要给不同的基类分别指定访问限定符。
子类继承父类的 protected 属性/方法之后,可以通过子类的指针去访问;子类也不能通过父类指针访问 protected 的成员。
Javascript 从 DOMNodeInserted 到 MutationObserver
发表于:
这段代码是在 Tamper Monkey 的 content.js 中发现的。被浏览器警告应该替换掉这种写法。
bn.addEventListener("DOMNodeInserted", o, s)
替换成
var [adder, remover] = ((bn, o, s) => {
var helper = { remove : () => {}, add : () => {} }
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var nodes = Array.prototype.slice.call(mutation.addedNodes);
nodes.forEach((node_) => { o(); helper.remove() });
});
});
helper.remove = () => observer.disconnect();
helper.add = () => observer.observe(bn, { childList: true, subtree: true, };
return [helper.add, helper.remove];
})(bn, o, s);
adder()
bn.removeEventListener("DOMNodeInserted", o, s)