C++ 中文周刊 第7期

reddit/hackernews/lobsters/meetingcpp摘抄一些c++动态。

每周更新

周刊项目地址 github在线地址知乎专栏

欢迎投稿,推荐或自荐文章/软件/资源等,请提交 issue


资讯

编译器信息最新动态推荐关注hellogcc公众号

本周周报github直达

文章

和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 - chris.rohlf@gmail.com */

#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++部分

项目


本文永久链接

看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持! 觉得写的不错那就给点吧, 在线乞讨 微信转账