layout | title |
---|---|
post |
第七期 |
从reddit/hackernews/lobsters/meetingcpp摘抄一些c++动态。
每周更新
欢迎投稿,推荐或自荐文章/软件/资源等,请提交 issue
和rust对比std::clamp实现 测试float场景 发现llvm的clamp实现有问题,并且给了个fix参考
介绍了一下ECS需要的数据结构和例子,具体例子可以去看ENTT
介绍一个提案https://vorbrodt.blog/wp-content/uploads/2021/04/n3339.pdf 以及实现https://github.com/mvorbrodt/blog/blob/master/src/deep_ptr.hpp 其实就是控制什么时候来拷贝
https://en.cppreference.com/w/cpp/numeric/constants c++20的新功能,可以看这段代码
#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <numbers>
#include <string_view>
struct two_t {};
template <class T>
constexpr auto operator^(T base, two_t) { return base * base; }
int main()
{
using namespace std::numbers;
constexpr two_t ²;
std::cout << "The answer is " <<
(((std::sin(e)^²) + (std::cos(e)^²)) +
std::pow(e, ln2) + std::sqrt(pi) * inv_sqrtpi +
((std::cosh(pi)^²) - (std::sinh(pi)^²)) +
sqrt3 * inv_sqrt3 * log2e * ln2 * log10e * ln10 *
pi * inv_pi + (phi * phi - phi)) *
((sqrt2 * sqrt3)^²) << '\n';
auto egamma_aprox = [] (unsigned const iterations) {
long double s = 0, m = 2.0;
for (unsigned c = 2; c != iterations; ++c, ++m) {
const long double t = std::riemann_zeta(m) / m;
(c & 1) == 0 ? s += t : s -= t;
}
return s;
};
constexpr std::string_view γ {"0.577215664901532860606512090082402"};
std::cout
<< "γ as 10⁶ sums of ±ζ(m)/m = "
<< egamma_aprox(1'000'000) << '\n'
<< "γ as egamma_v<float> = "
<< std::setprecision(std::numeric_limits<float>::digits10 + 1)
<< egamma_v<float> << '\n'
<< "γ as egamma_v<double> = "
<< std::setprecision(std::numeric_limits<double>::digits10 + 1)
<< egamma_v<double> << '\n'
<< "γ as egamma_v<long double> = "
<< std::setprecision(std::numeric_limits<long double>::digits10 + 1)
<< egamma_v<long double> << '\n'
<< "γ with " << γ.length() - 1 << " digits precision = " << γ << '\n';
}
//The answer is 42
//γ as 10⁶ sums of ±ζ(m)/m = 0.577215
//γ as egamma_v<float> = 0.5772157
//γ as egamma_v<double> = 0.5772156649015329
//γ as egamma_v<long double> = 0.5772156649015328606
//γ with 34 digits precision = 0.577215664901532860606512090082402
利用constexpr做编译期的取位
#pragma once
#include <limits>
#include <type_traits>
#include <stdexcept>
#include <cmath>
#include <cstdint>
// solution 1
// can specify number of digits at run-time as the second parameter
// slowest due to 2 function calls
template<typename T>
requires std::is_floating_point_v<T>
auto runtime_round(T v, unsigned char d)
{
auto p = std::pow(T(10), T(d));
if(std::abs(v) > std::numeric_limits<T>::max() / p) // v * p would overflow
throw std::overflow_error("rounding would overflow");
return std::round(v * p) / p;
}
// sloution 2
// if used only with other constexpr the result will be evaluated
// entirely at compile time meaning no runtime cost :)
// recursive template to compute B^E at compile time
// result is stored as a static variable 'value' of type T
template<std::uint64_t B, unsigned char E, typename T>
requires std::is_arithmetic_v<T>
struct power_of
{
static constexpr T value = T(B) * power_of<B, E - 1, T>::value;
};
// terminating template for the recursion one above once E == 0
template<std::uint64_t B, typename T>
requires std::is_arithmetic_v<T>
struct power_of<B, 0, T>
{
static constexpr T value = T(1);
};
template<std::uint64_t B, unsigned char E, typename T>
inline constexpr auto power_of_v = power_of<B, E, T>::value;
// recursive function template to calculate b^e
// if both parameters are constexpr it will evaluate at compile time
// otherwise it will evaluate at run time
// returns the result as type T
template<typename T>
requires std::is_arithmetic_v<T>
constexpr T power_of_f(std::uint64_t b, unsigned char e)
{
return e == 0 ? T(1) : T(b) * power_of_f<T>(b, e - 1);
}
// given a value 'v' return +1 if v is >= 0, otherwise return -1
template<typename T>
requires std::is_arithmetic_v<T>
constexpr auto my_sign(T v)
{
return v >= T(0) ? T(1) : T(-1);
}
// given a value 'v' return it's absolute value
template<typename T>
requires std::is_arithmetic_v<T>
constexpr auto my_abs(T v)
{
return v >= T(0) ? v : -v;
}
// round float/double/long double value 'v' to the nearest integer
// using compile time type conversions
template<typename T>
requires std::is_floating_point_v<T>
constexpr auto my_rnd(T v)
{
constexpr auto h = T(0.5) - std::numeric_limits<T>::epsilon();
return (std::int64_t)(v + h * my_sign(v));
}
// self explanatory :)
// though number of digits must be provided at compile time
// as the first template parameter 'D'
template<unsigned char D, typename T>
requires std::is_floating_point_v<T>
constexpr auto constexpr_round(T v)
{
/* option 1 */ //constexpr auto p = power_of_f<T>(10, D);
/* option 2 */ constexpr auto p = power_of_v<10, D, T>;
if(my_abs(v) > std::numeric_limits<T>::max() / p)
return v; // v * p would overflow
if(my_abs(v) * p > std::numeric_limits<std::int64_t>::max() - 1)
return v; // v * p would not fit in int64_t
return my_rnd(v * p) / p;
}
教你使用co_await
// startJob.cpp
#include <coroutine>
#include <iostream>
struct Job {
struct promise_type;
using handle_type = std::coroutine_handle<promise_type>;
handle_type coro;
Job(handle_type h): coro(h){}
~Job() {
if ( coro ) coro.destroy();
}
void start() {
coro.resume(); // (6)
}
struct promise_type {
auto get_return_object() {
return Job{handle_type::from_promise(*this)};
}
std::suspend_always initial_suspend() { // (4)
std::cout << " Preparing job" << '\n';
return {};
}
std::suspend_always final_suspend() noexcept { // (7)
std::cout << " Performing job" << '\n';
return {};
}
void return_void() {}
void unhandled_exception() {}
};
};
Job prepareJob() { // (1)
co_await std::suspend_never(); // (2)
}
int main() {
std::cout << "Before job" << '\n';
auto job = prepareJob(); // (3)
job.start(); // (5)
std::cout << "After job" << '\n';
}
//打印
//Before job
// Preparing job
// Performing job
//After job
#include <coroutine>
#include <iostream>
#include <optional>
#include <string_view>
#include <thread>
#include <vector>
std::jthread *thread;
template <typename T> struct future {
struct promise_type {
T value;
future get_return_object() {
return {std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_always initial_suspend() noexcept {
std::cout << "initial" << std::endl;
return {};
}
std::suspend_always final_suspend() noexcept {
std::cout << "final" << std::endl;
return {};
}
void return_value(T x) {
std::cout << "return value" << std::endl;
value = std::move(x);
}
void unhandled_exception() noexcept {}
~promise_type() { std::cout << "future ~promise_type" << std::endl; }
};
struct AwaitableFuture {
future &m_future;
bool await_ready() const noexcept { return false; }
void await_suspend(std::coroutine_handle<> handle) {
std::cout << "await_suspend" << std::endl;
*thread = std::jthread([this, handle] {
std::cout << "Launch thread: " << std::this_thread::get_id()
<< std::endl;
m_future.coro.resume();
handle.resume();
});
}
T await_resume() {
std::cout << "await_resume" << std::endl;
return m_future.coro.promise().value;
}
~AwaitableFuture() { std::cout << "~AwaitableFuture" << std::endl; }
};
std::coroutine_handle<promise_type> coro;
future(std::coroutine_handle<promise_type> coro) : coro{coro} {}
~future() {
std::cout << "~future" << std::endl;
if (coro)
coro.destroy();
}
AwaitableFuture operator co_await() {
std::cout << "co_await" << std::endl;
return {*this};
}
};
template <typename F, typename... Args>
future<std::invoke_result_t<F, Args...>> async(F f, Args... args) {
std::cout << "async" << std::endl;
co_return f(args...);
}
struct task {
struct promise_type {
task get_return_object() { return {}; }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() noexcept {}
~promise_type() { std::cout << "~task promise_type" << std::endl; }
};
~task() { std::cout << "~task" << std::endl; }
};
int square(int x) {
std::cout << "square in thread id " << std::this_thread::get_id()
<< std::endl;
return x * x;
}
task f() {
auto squared6 = co_await async(square, 6);
std::cout << "Write " << squared6
<< " from thread: " << std::this_thread::get_id() << std::endl;
}
int main() {
std::jthread thread;
::thread = &thread;
f();
return 0;
}
通过co_return返回future
某些场景下本应该copy elision的场景并没有
#include <string>
#include <string_view>
// Some type that is expensive to copy, non-trivial to destroy, and cheap but
// not free to move.
struct Widget {
std::string s;
};
void consume(Widget w);
Widget doSomeVeryComplicatedThingWithSeveralArguments(
int arg1, std::string_view arg2);
//正常,一个widget,RVO
void someFunction() {
consume(doSomeVeryComplicatedThingWithSeveralArguments(123, "hello"));
}
//没优化,复制了一份????
void someFunctionV2() {
auto complicatedThingResult =
doSomeVeryComplicatedThingWithSeveralArguments(123, "hello");
consume(complicatedThingResult);
}
//还是没优化??
void someFunctionV3() {
auto complicatedThingResult =
doSomeVeryComplicatedThingWithSeveralArguments(123, "hello");
consume(std::move(complicatedThingResult));
}
//优化了,但这里没有临时的widget,是通过lambda绕过去的
void someFunctionV4() {
auto const complicatedThingResult = []{
return
doSomeVeryComplicatedThingWithSeveralArguments(
123, "hello"
);
};
consume(complicatedThingResult());
}
这里是假定complicatedThingResult可能会被后面的改掉,所以不敢move
具体看这个代码演示https://godbolt.org/z/n9nToG9KE
Overloaded trick在c++ 11是用不了的,所以引入了个make,和make_unique/make_tuple差不多, 加一层
template <class... Fs> struct overloaded;
template <class F1> struct overloaded<F1> : F1 {
using F1::operator();
overloaded(F1 f0) : F1(f0) {}
};
template <class F1, class... Fs>
struct overloaded<F1, Fs...> : F1, overloaded<Fs...> {
using F1::operator();
using overloaded<Fs...>::operator();
overloaded(F1 f0, Fs... fs) : F1(f0), overloaded<Fs...>(fs...) {}
};
template <typename... Fs>
overloaded<Fs...> make_overloaded_function(Fs... fs) {
return overloaded<Fs...>(fs...);
};
class app {
public:
explicit(true) app(int, double) { }
};
struct config : boost::di::config {
struct mocks {
template <class T, class TInitialization, class TMemory, class... TArgs>
auto get(const TInitialization&, const TMemory&, TArgs&&... args) const
-> boost::di::aux::owner<T*> {
std::clog << typeid(T).name() << '\n';
return new T{args...};
}
};
auto provider(...) const { return mocks{}; }
};
int main() {
boost::di::create<app>(boost::di::make_injector<config>());
// prints app
// int
// double
}
这里介绍了一个用bpftrace/uprobe定位c++问题的方法,很有趣
一个简单的uprobe
比如你想观测这个函数
namespace vast {
std::vector<std::pair<offset, predicate>> resolve(const expression& expr, const type& t);
}
首先,通过c++filt拿到真实的符号名 _ZN4vast7resolveERKNS_10expressionERKNS_4typeE
然后写bpftrace脚本,这里vast是被观测到函数
struct vector {
void* first;
void* last;
void* end_of_storage;
};
uretprobe:/usr/bin/vast:_ZN4vast7resolveERKNS_10expressionERKNS_4typeE {
$vec = (struct vector*)reg("ax");
printf("resolved %d offset+predicate pairs\n", ($vec->last - $vec->first) / 464);
}
这个语法差不多,像awk
PROBE {
ACTION_BLOCK
}
直接运行就行了
sudo bpftrace simple.bt
Attaching 1 probe...
resolved 5 offset+predicate pairs
resolved 7 offset+predicate pairs
这里需要一点基本的c++知识,需要了解基本的内存结构,比如vector,比如vtable
后面又演示了如何观测对象,涉及到vtable, 在博客的最后
mozilla firefox团队使用tsan的经验总结。值得一看,rust一样会被tsan抓到问题
作者设计了一个allocator,内置了Memory Sanitizer 功能,实现方案是userfaultfd 需要了解这个api
cat tests/uninit_read.c
/* iso_alloc uninit_read.c
* Copyright 2021 - [email protected] */
#include "iso_alloc.h"
#include "iso_alloc_internal.h"
int main(int argc, char *argv[]) {
while(1) {
uint8_t *p = iso_alloc(1024);
uint8_t drf = p[128];
p[256] = drf;
iso_free(p);
}
return OK;
}
$ LD_LIBRARY_PATH=build/ build/uninit_read
[ABORTING][86027](src/iso_alloc_sanity.c:78 _page_fault_thread_handler()) Uninitialized read detected on page 7fb6ce3cf000 (1024 byte allocation)
Aborted (core dumped)
简单说有个线程检测分配,用userfaultfd挂着,放到一个未初始化 page list里,如果监测到写,就说明初始化了,就移出去,如果监测到读,直接挂掉,像上面演示的效果那样
isoalloc设计就是为了内存安全的,考虑了很多点子,可以了解一下。对于实际的写代码的人,用MSan就好了
static inline constexpr unsigned long long int x = 42;
long int long inline unsigned constexpr static y = 42;
虽然都很辣眼睛,但是尽量用第一种写法
看这个样例 https://en.cppreference.com/w/cpp/algorithm/shift
作者讨论了如果operator << 里面没有判断的逻辑,打印会是啥样的
这个talk讲的是如何设计稳定的c++ sdk 导出了一套c的虚表实现,拆分出c++部分
- https://github.com/fschuetz04/simcpp20 一个simpy的c++实现,使用coroutine