Releases: wanghenshui/cppweeklynews
C++ 中文周刊 第86期
C++ 中文周刊 第86期
从reddit/hackernews/lobsters/meetingcpp/purecpp/知乎/等等摘抄一些c++动态
公众号也有了
弄了个qq频道,手机qq点击进入
欢迎投稿,推荐或自荐文章/软件/资源等
可以贴在下一期草稿里 草稿链接
2022 10 29
资讯
标准委员会动态/ide/编译器信息放在这里
编译器信息最新动态推荐关注hellogcc公众号 2022-10-26 第173期
2022 purecpp大会ppt和视频
http://www.purecpp.cn/detail?id=2322
文章
介绍MSVC在copy elision move elision 的改进
consteval auto to_number(auto str) {
int value;{}
std::from_chars(std::cbegin(str), std::cend(str), value);
return value;
}
static_assert(42 == to_number(std::string_view{"42"}));
调试工具。老生常谈了
简单说,这个就是sqlite里的printf遇到%Q %q %w且字符串 非常非常非常大,才会触发这个CVE。且sqlite本身不会触发
sqlite不是100%分支覆盖么,为啥还有问题?分支覆盖了并不代表没问题。fuzzer也有可能覆盖不到这种非常非常大的数据的场景
另外牛逼一点的静态分析工具也能查到,但是基本上就是个告警,但没人用/当回事
使用字符串 format %一定要注意。这种坑不是一两个了。苹果wifi那个CVE也是%惹的祸
if-switch里可以定义了。这样省一行。比如
switch (Foo foo; foo.getValue()) {
case 1: //...
break;
case 2: //...
break;
case 3: //...
break;
default:
//...
break;
}
...
if (int val = getValue(); val<10) {
std::cout << "val smaller than 10: " << val << '\n';
} else if (val < 5) {
std::cout << "val smaller than 5: " << val << '\n';
} else {
std::cout << "val is bigger than 10: " << val << '\n';
}
c++17就可以了。
try
{
Block1;
}
catch (Type1 ex1)
{
Block2;
}
catch (Type2 const& ex2)
{
Block3;
}
作者说了一个场景
Block1里有两个栈对象,第一个析构的时候抛异常,被catch了。第二个也跑异常,此时已经catch过了,所以直接std::terminate
c++是没有finally收尾+析构这种模式的,上面这种场景要 如何规避?
流式压缩,效果比facebook哪个gorila还好,挺有意思
视频
整数溢出导致。。。-Wno-error=deprecated
-Wsign-conversion
-Wconversion
你总得用一个吧
CPPCON
介绍 c++23 的。没啥说的
协程上手,很长
没啥说的
这个有手把手调代码演示,可以看看
代码在这里 https://github.com/sankurm/generic-pipeline
贴一下代码
#include <string>
#include <iostream>
#include <exception>
#include <type_traits>
#include <functional>
#include <fstream>
//The generic implementation also takes care of the return type of Callable being different than T
template<typename T, typename Callable>
auto operator|(T&& val, Callable&& fn) -> typename std::result_of<Callable(T)>::type {
return std::forward<Callable>(fn)(std::forward<T>(val));
}
//Pre-C++17 code without std::optional
//Code relies on special values like empty string, kafka_config to be convertible to bool and return bools to determine success of a step
namespace
{
struct env_error : public std::exception {};
struct file_error : public std::exception {};
struct json_error : public std::exception {};
struct creation_error : public std::exception {};
struct connect_error : public std::exception {};
struct subscribe_error : public std::exception {};
std::string get_env(std::string&& varname) {
if (/* varname not set OR value is empty */false) { throw env_error{}; }
return "/config/kafka.json";
}
std::string get_file_contents(std::string&& filename) {
std::ifstream file(filename, std::ios::in);
if (!file && false) { throw file_error{}; }
return "file-contents-blah-blah";
}
struct kafka_config
{
/* url etc. */
operator bool() { return true; }
};
enum config_type { json, xml, yaml, config_map };
template<config_type format>
kafka_config parse_kafka_config(std::string&& config) {
if (/* parsing fails == */ false) { throw json_error{}; }
return kafka_config{};
}
struct kafka_consumer
{
kafka_consumer(const kafka_config& config) {}
kafka_consumer(kafka_config&& config) {}
bool connect() { return true; }
bool subscribe() { return true; }
operator bool() { return true; }
};
kafka_consumer create_kafka_consumer(kafka_config&& config) {
return kafka_consumer{std::move(config)};
}
kafka_consumer connect(kafka_consumer&& consumer) {
if (!consumer.connect()) { throw connect_error{}; }
return consumer;
}
auto subscribe = [](kafka_consumer&& consumer) {
if (!consumer) { throw subscribe_error{}; }
consumer.subscribe();
return consumer;
};
//Invoking an operation taking more than 1 argument
//std::bind solution
kafka_consumer init_kafka() {
//using namespace std::string_literals;
//Can use "kafka-config-filename"s as they need C++14
return std::string("kafka-config-filename")
| get_env
| get_file_contents
| parse_kafka_config<xml>
| create_kafka_consumer
| connect
| subscribe;
}
}
int main(int argc, char** argv) {
auto consumer = init_kafka();
if (consumer) { std::cout << "Consumer creation successful\n"; }
else { std::cout << "Consumer creation failed\n"; }
return 0;
}
看个乐。没有scheduler的pipeline操作没啥意义,除了语法糖让自己爽一爽,没别的用处
开源项目需要人手
- asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群384042845和作者对线
- pika 一个nosql 存储, redis over rocksdb,非常需要人贡献代码胖友们, 感兴趣的欢迎加群294254078前来对线
新项目介绍/版本更新
- tiny-optional 一个optional库
- libcfg-cpp 一个配置库
- sclui 一个 终端库
- cheap (C++ HTML Element Apparator)
生成html的,比如生成const std::string elem_str = get_element_str(div(span("first"), img("src=test.jpg"_att)));
<div> <span>first</span> <img src="test.jpg" /> </div>
工作招聘
不被开除就不错了
看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持!
C++ 中文周刊 第85期
从reddit/hackernews/lobsters/meetingcpp/purecpp知乎/等等摘抄一些c++动态
弄了个qq频道,手机qq点击进入
欢迎投稿,推荐或自荐文章/软件/资源等
公众号也有了
不过没有留言功能
可以贴在下一期草稿里 草稿链接
2022 1021
资讯
标准委员会动态/ide/编译器信息放在这里
编译器信息最新动态推荐关注hellogcc公众号 本周更新 2022-10-19 第172期
2022全球C++及系统软件技术大会演讲嘉宾PPT集合:
https://city.boolan.com/2022_CPPSummit.pdf
2020年的在这里
https://city.boolan.com/2020CPP.pdf
页数很多。没有视频。走马观花随便看看
2022 10.23 purecpp C++大会直播 链接 http://t.csdn.cn/uBRwn
直播内容
文章
基础概念
当我们有一个带GC的编程语言runtime的时候,这个GC该什么时候进行垃圾回收?一般来说,一个runtime会有一个heap limit(L),当程序消耗内存量达到limit后,则会进行垃圾回收,使得内存使用量下降到limit以下。假设程序自己的live set size(无法被GC清掉的,还在用着的内存)是S, 那一般来说,会设置成L = N * S,其中N是一个2之类的常数。这代表着,一个带GC的程序的最大内存使用量,应该是手动内存管理的内存使用量的两倍。但,这其实并不合理!我们在paper里面对垃圾回收进行数学建模,然后寻找一个这个问题的最优解。我们最后发现,应该设置成L = S + N * Sqrt(S),就是说 - S越大,我们对应于原先的算法,应该给的内存越小!这时候,我们的算法在v8 javascript engine上可以比原先的GC快30%(同等内存),又或者同等时间下节省15%的内存
我咋感觉和c++里的vector扩容预留空间不能是简单翻倍而是1.5倍 那种场景有点像,也就是说那种大vector的扩容分配空间可以用这种算法再优化一波
再比如各种buffer pool 管理策略,内存都是钉死的,怎么找到最佳的S呢?
有点意思
给你一个整体的视角,从流水线到cache到虚拟内存 。看图可以点这个
讲SEH的。
__try
{
Block1;
}
__except (FilterExpression)
{
Block2;
}
__finally
{
Block3;
}
乍一看很常规是不是,你再仔细看看有没有遗漏?
FilterExpression有可能异常!如果FilterExpression异常,它是被外层的try包起来的,也就是说,会继续走到__except,会匹配到FilterExpression,会继续异常
最终堆栈溢出程序挂掉
一般遇不到,遇到就会很莫名其妙
之前也聊过,CTAD可能有害,所以要加上告警规避这种场景,但是简单的告警还有可能漏掉某些场景,建议直接-Werror=ctad
vector在move场景是比较保守的,如果T本身不是noexcept move,就copy。本身也是怕T本身move的副作用引发失败,退而求其次保守copy
考虑一个T实现
struct Widget {
std::list<int> m_ = std::list<int>(1000);
// MSVC's std::list isn't nothrow movable
};
std::vector<Widget> v(10);
try {
v.reserve(v.capacity()+1); // reallocate the buffer
} catch (...) {
// is v still in its old state?
}
正常的场景,不在乎copy的代价,只要保证Widget没问题就行,强异常处理bad alloc。这样能用没啥问题
如果强异常场景,还要尽可能的move,m_的move可能会有问题,那只好给Widget一个noexcept的move
struct Widget {
std::list<int> m_ = std::list<int>(1000);
// MSVC's std::list isn't nothrow movable
explicit Widget() = default;
Widget(Widget&&) noexcept = default; // !!
Widget(const Widget&) = default;
Widget& operator=(Widget&&) = default;
Widget& operator=(const Widget&) = default;
};
std::vector<Widget> v(10);
又想尽可能的move,又想处理bad alloc,其他异常无所谓,做不到,只能自己造一个std::vector了
这个就是作者讨论的类似CAP的那种三角问题
说实话我没怎么理解明白
使用核心内建的功能,而不是库提供的功能
比如 alignof 好过 std::alignment_of_v
static_assert(std::alignment_of_v<Widget> == 8); // worse
static_assert(alignof(Widget) == 8); // better
比如alignas好过 std::aligned_storage_t
std::aligned_storage<sizeof(Widget)> data; // utterly wrong
std::aligned_storage_t<sizeof(Widget)> data; // still kind of wrong
std::aligned_storage_t<sizeof(Widget), alignof(Widget)> data; // correct but bad
alignas(Widget) char data[sizeof(Widget)]; // correct and better
比如char好过std::bytes
std::byte data[100]; // worse
char data[100]; // better
int data[10];
// worse
void mycopy(std::byte *dst, const std::byte *src, size_t n);
mycopy((std::byte*)data, (std::byte*)(data+5), 5 * sizeof(int));
// better
void mycopy(void *dst, const void *src, size_t n);
mycopy(data, data+5, 5 * sizeof(int));
lambda 好过bind
range for好过std::for_each
不列代码了
struct好过tuple有名字信息
placement new 好过construct_at
alignas(Widget) char data[sizeof(Widget)];
Widget *pw = std::construct_at((Widget*)data, x, y); // worse
Widget *pw = ::new ((void*)data) Widget(x, y); // better
当然这些都是习惯。不强求
依赖计算越长,CPU pipeline越差
为啥unique_ptr构造函数不把make_unique的活给干了??语义不清晰。T里的构造很让人困惑
#include <bitset>
constexpr std::bitset<4> b1{"0011"};
static_assert(0b0011 == b1.to_ulong());
constexpr std::bitset<4> b2{0b0011};
static_assert(b1 == b2);
这玩意谁用啊。folly有个省空间的bitset
signum是个很常规的函数
#include <cmath>
float signum_safe(float Value) {
if( Value < 0.0 )
return -1.0;
if( Value > 0.0 )
return 1.0;
if( std::isnan(Value) )
return Value;
return 0.0;
}
文章讨论用avx512 vfixupimm来实现,并且如何妥善的处理isnan?
视频
直接看代码吧 https://compiler-explorer.com/z/ee8MGrrEY
bind的一种强化
cppcon视频开始放流了,一天一个基本上
讨论值语义的。没能看完
阿里许传奇讲他们应用编译优化的一些经验,LTO, 编译cache,module之类的。没看完。这个英语有点难受。我估计会有中文版的
给我整困了
开源项目需要人手
- asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群384042845和作者对线
- pika 一个nosql 存储, redis over rocksdb,非常需要人贡献代码胖友们, 感兴趣的欢迎加群294254078前来对线
新项目介绍/版本更新
- snatch 又一个test框架 c++20
- mGBA 0.10.0 gba模拟器
- ezpz 一个parser框架
看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持!
C++ 中文周刊 第84期
C++ 中文周刊 第84期
从reddit/hackernews/lobsters/meetingcpp/purecpp知乎/等等摘抄一些c++动态
微信公众号下周能上吧
弄了个qq频道,手机qq点击进入
欢迎投稿,推荐或自荐文章/软件/资源等
可以贴在下一期草稿里 草稿链接
2022 10 14
资讯
标准委员会动态/ide/编译器信息放在这里
编译器信息最新动态推荐关注hellogcc公众号 本周更新2022-10-12 第171期
文章
正则表达式性能对比,直接贴结果吧。
环境 AMD 线程撕裂者 3960X (Zen2) 3.8 GHz Ubuntu 20.04.5 LTS
蓝的最好红的最差,std::regex就不提了,丢人,你看看rust的regex表现就不错
crte是编译期的 hyperscan是intel开源c++实现,hyperscan遥遥领先
对智能指针取地址,std::addressof, 别用 & 使用上可能有异常场景(比如指针释放)。一半都会有addressof成员函数啥的
省流:CopyFile2 win8之后支持
他的这个压测有点意思
本文的结论如下: 无栈的切换速度要远高于有栈。 无栈协程更加适合IO场景。 无栈协程相比普通函数会有额外开销。
而迄今为止 io uring 已经支持了 48 种异步操作,这四种操作只是冰山一角。本文希望讨论一下如何为 Seastar 加入一部分网络 IO 的 io_uring 支持。
可以看看。有点意思。另外这个哥们也是seastar/ceph开发者,博客写的不错
这个是iouring相关MR scylladb/seastar#1235
这几个经验还是挺有意思的,比如magic字符串,以及多租户场景下轻易别退出。。。(这个我也遇到过)
很值得一看,一看就知道线上的惨烈了,我是笑着看完的
concept作为接口的一部分
#include <concepts>
void foo(int i);
void bar(std::same_as<int> auto i);
int main() {
foo(42.0); // 隐式转换
bar(42.0); // 编译不过
}
c++程序员没有不知道的吧,没有就再普及一遍
草药老师又在讲设计了。没细看
为啥用wasm不用luajit lua +jit不能做沙盒么?
视频
推荐了一堆网络库
讲的挺基础的。可以看看
开源项目需要人手
- asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群384042845和作者对线
- pika 一个nosql 存储, redis over rocksdb,非常需要人贡献代码胖友们, 感兴趣的欢迎加群294254078前来对线
新项目介绍/版本更新
- advanced-bitfield 位域操作
- awesome-iouring 资料汇总
- glaze 一个json库
看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持!
C++ 中文周刊 第83期
C++ 中文周刊 第83期
从reddit/hackernews/lobsters/meetingcpp/purecpp知乎/等等摘抄一些c++动态
弄了个qq频道,手机qq点击进入
欢迎投稿,推荐或自荐文章/软件/资源等
可以贴在下一期草稿里 草稿链接
2022 1008
资讯
标准委员会动态/ide/编译器信息放在这里
编译器信息最新动态推荐关注hellogcc公众号 本周更新 2022-10-05 第170期
文章
直接贴个代码 https://godbolt.org/z/Yxc58vrWW
cppfront的设计。看个乐。草木大哥上次玩的constraint也探索了好几年。这个玩意我估计也得玩几年。不会进。只是提供个思路
众所周知,range for里的临时变量左值有问题
class Keeper {
std::vector<int> data{2, 3, 4};
public:
~Keeper() { std::cout << "dtor\n"; }
// Returns by reference
auto& items() { return data; }
};
// Returns by value
Keeper GetKeeper() {
return {};
}
void Use() {
// ① Use the result of GetKeeper and return
// over items
for(auto& item : GetKeeper().items()) {
std::cout << item << '\n';
}
}
这个遍历很有可能挂掉。UB。但是我非要这么写,keeper类怎么设计呢?
class Keeper {
std::vector<int> data{2, 3, 4};
public:
~Keeper() { std::cout << "dtor\n"; }
auto& items() & { return data; }
// ④ For rvalues, by value with move
auto items() && { return std::move(data); }
};
注意这两个items后面的&限定,两种限定约定了被调用的时候走左还是右值,如果是range for循环,就调用第二个items,救一下data的生命,就没问题了。
我觉得还是尽量别range for里乱搞。容易误用。有的类设计类这种方法,如果有的类没这么设计,不就完了。
周所周知,临时变量的生命周期是一行,来一个复杂的例子
考虑一行上锁自动解锁
template<typename> struct LockableData;
namespace std
{
template<typename Data>
struct default_delete<LockableData<Data>>
{
void operator()(LockableData<Data>* p)
const noexcept { p->m.unlock(); }
};
}
template<typename Lockable>
struct [[nodiscard]] LockedData
{
LockedData(Lockable* l = nullptr) : l(l)
{ if (l) l->m.lock(); }
auto operator->() const noexcept
{ return std::addressof(l->data); }
private:
std::unique_ptr<Lockable> l;
};
template<typename Data>
struct LockableData
{
LockedData<LockableData> Lock() { return this; }
private:
friend struct LockedData<LockableData>;
friend struct std::default_delete<LockableData>;
std::mutex m;
Data data;
};
使用例子
struct WidgetInfo
{
std::string name;
int times_toggled = 0;
};
class Widget
{
LockableData<WidgetInfo> info;
public:
void SetName(std::string name)
{
auto lock = info.Lock();
lock->name = name;
lock->times_toggled = 0;
}
std::string GetName()
{
auto lock = info.Lock();
return lock->name;
}
void Toggle()
{
{ // scope the lock
auto lock = info.Lock();
lock->times_toggled++;
}
FlipSwitchesRandomly();
}
};
目前来看还是没啥问题,但是要多一个lock,很自然的,你想到了省略这一行
template<typename Data>
struct LockableData
{
LockedData<LockableData> Lock() { return this; }
auto operator->() { return Lock(); } // NEW!
private:
friend struct LockedData<LockableData>;
friend struct std::default_delete<LockableData>;
std::mutex m;
Data data;
};
class Widget
{
LockableData<WidgetInfo> info;
public:
void SetName(std::string name)
{
auto lock = info.Lock();
lock->name = name;
lock->times_toggled = 0;
}
std::string GetName()
{
return info->name; // lock-read-unlock
}
void Toggle()
{
info->times_toggled++; // lock-modify-unlock
FlipSwitchesRandomly();
}
};
问题来了。info->调用生成了一个临时对象,临时对象这一行结束就释放了,可能会出现读的不一样的问题,但这问题不大,真正的问题是这种用法可能导致锁两次
比如上面这个toggle,伪代码
// Evaluate right hand side
LockedData<WidgetInfo> lock1 = info.operator->();
int rhs = std::max(lock1->times_toggled, 10);
// Evaluate left hand side
LockedData<WidgetInfo> lock2 = info.operator->();
// Perform the assignment
lock2->times_toggled = rhs;
// Destruct temporaries in reverse order of construction
destruct lock2;
destruct rhs;
destruct lock1;
明显锁了两次。可能->这个方法过于有问题,我直接调用,比如
std::string GetName()
{
return info.Lock()->name;
}
应该不会有问题了吧, 如果toggle这么实现
void Toggle()
{
// suspicious double-lock - more likely to be spotted in code review
info.Lock()->times_toggled = std::max(info.Lock()->times_toggled, 10);
FlipSwitchesRandomly();
}
也是有同样问题的
RAII的烦恼也很多啊。解决方法可能是out_ptr或者std::synchronized_value folly::synchronize这种类似的玩意。别自己写了。可能想不到
不太懂
分析了一波,是编译器bug。msvc 16.10 以下的版本有问题,修复记录 https://devblogs.microsoft.com/cppblog/cpp20-coroutine-improvements-in-visual-studio-2019-version-16-11/
用concept实现crtp。之前也介绍过类似的
// we create a concept can_work to check if do_work is implemented
// this will describe our interface
template <typename T>
concept can_work = requires(T t) {
t.do_work();
};
// now we apply this concept to an empty type which represents a worker (or our base class)
template<can_work T>
struct worker : public T {};
// now create a concrete worker (corresponding derived) where we implement do_work
struct concrete_worker {
void do_work() {
// ...
}
};
// nice to have: an alias for our concrete worker
using my_worker = worker<concrete_worker>;
//...
// which we can use now
my_worker w;
w.do_work();
面向接口的感觉
没啥说的。c++23就能用了。之前你可以用absl的或者boost的。都差不多
一个COW vector大概的样子
template <class T>
class CowVector {
struct State {
std::atomic<int> ref;
size_t size;
size_t capacity;
T elements[];
}
State* state;
// if we're not unique, we need to allocate
// a new State and copy the elements.
// if we are unique, this is a no-op.
void copy_on_write();
public:
// copy constructor *never* allocates.
// just increments ref-count
CowVector(CowVector const& rhs)
: state(rhs.state)
{
++state->ref;
}
// and the mutable and const accessors do different things
auto operator[](size_t idx) -> T& {
copy_on_write();
return state->elements[idx];
}
auto operator[](size_t idx) const -> T const& {
return state->elements[idx];
}
};
怎么更干净更灵活的copy_on_write? 这套代码怎么用 Deducing this 改写
template <class T>
class CowVector {
public:
auto operator[](this CowVector& self, size_t idx) -> T&;
auto operator[](this CowVector const& self, size_t idx) -> T const&;
};
Self应该模版化
template <class T>
class CowVector {
struct State { ... };
State* state;
// this one (potentially) copies
auto get_state() -> State*;
// this one doesn't, because const
auto get_state() const -> State const* { return state; }
public:
template <class Self>
auto operator[](this Self& self, size_t idx)
-> std::copy_const_t<Self, T>&
{
return self.get_state()->elements[idx];
}
};
里面还讨论了很多边角场景,感兴趣的可以看看
一个map存数据,如果存在就不插入
object* retrieve_or_create(int id)
{
static std::unordered_map<int, std::unique_ptr<object>> m;
// see if the object is already in the map
auto [it,b] = m.emplace(id, nullptr);
// create it otherwise
if(b) it->second = std::make_unique<object>(id);
return it->second.get();
}
很常规。问题在于object可能非常大,可能构造异常。try catch一下,正好有try_emplace
这个接口
object* retrieve_or_create(int id)
{
static std::unordered_map<int, std::unique_ptr<object>> m;
auto [it,b] = m.try_emplace(id, std::make_unique<object>(id));
return it->second.get();
}
但是问题并没有解决,我们希望的是,直到需要调用make的时候,再调用。推迟到emplace 那一刻
template<typename F>
struct deferred_call
{
using result_type=decltype(std::declval<const F>()());
operator result_type() const { return f(); }
F f;
};
object* retrieve_or_create(int id)
{
static std::unordered_map<int, std::unique_ptr<object>> m;
auto [it,b] = m.try_emplace(
id,
deferred_c...
C++ 中文周刊 第82期
C++ 中文周刊 第82期
从reddit/hackernews/lobsters/meetingcpp/purecpp/知乎/等等摘抄一些c++动态
弄了个qq频道,手机qq点击进入
欢迎投稿,推荐或自荐文章/软件/资源等
可以贴在下一期草稿里 草稿链接
2022 1002
大家国庆快乐
资讯
标准委员会动态/ide/编译器信息放在这里
编译器信息最新动态推荐关注hellogcc公众号 本周更新 2022-09-28 第169期
文章
除了贵和积热之外没啥缺点。zen4太贵了。本来打算双十一拼一台。看样子只能考虑明年618再拼了。
今年可以双十一攒个5950x,加b550不到四千。内存也便宜
struct s {
static constexpr auto operator()() { return 1; }
};
auto l = [] static { return 2; };
static_assert(3 == s{}() + l());
static_assert(3 == s::operator()() +
decltype(l)::operator()());
没啥说的。补全一下
有了ssize支持,可以不考虑符号了,
for (int i = 0; i < ssize(vec); ++i)
std::cout << i << ": " << vec[i] << '\n';
range遍历 + 索引
int main() {
std::vector vec { 1, 2, 3, 4, 5};
for (int i = 0; const auto& elem : vec)
std::cout << i++ << ": " << elem << '\n';
}
说实话有点别扭,没有go那种 i, v = range
那种感觉好, 聊胜于无吧
组合view
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector vec { 1, 2, 3, 4, 5};
for (int i = 0; const auto& elem : vec | std::views::reverse)
std::cout << i++ << ": " << elem << '\n';
}
使用range view算法
void printReverse(auto cont) {
std::ranges::for_each(cont | std::views::reverse,
[i=0](const auto& elem) mutable {
std::cout << i++ << ' ' << elem << '\n';
}
);
}
看不懂
科普一下
还是科普
这个其实就是之前那个英语博客的转述
草药老师关于他cppfront语法的一些需求的实现。没细看
介绍format以及llvm的支持程度。基本都支持除了chrono
内联场景的前缀字符串前缀数组可能会被编译器优化掉,减少二进制大小
比如
const char * str1 = "dear friend";
const char * str2 = "dear friend";
return str1 == str2;
void oops(std::vector<std::string>& v)
{
set_name(v.front.c_str());
// ^^^^^ error: A pointer to a bound function
// may only be used to call the function
}
忘记加括号了
有些功能比如move迭代器之类的存在用不好的场景。导致莫名其妙的影响性能。
视频
类型擦除的方法。之前也讲过. 就是藏函数指针
class animal_view {
public:
template <typename Speakable>
explicit animal_view(const Speakable &speakable)
: object{&speakable},
speak_impl{// create a lambda that knows how to unpack the stored object
[](const void *obj) {
return static_cast<const Speakable *>(obj)->speak();
}} {}
void speak() const { speak_impl(object); }
private:
// void * to object of unknown type
const void *object;
// function pointer that knows how to use unknown type
void (*speak_impl)(const void *);
};
void do_animal_things(animal_view animal) { animal.speak(); }
int main() {
struct Cow {
void speak() const { fmt::print("Mooo\n"); }
};
struct Sheep {
void speak() const { fmt::print("Baaa\n"); }
};
do_animal_things(animal_view{Cow{}});
do_animal_things(animal_view{Sheep{}});
}
开源项目需要人手
- asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群384042845和作者对线
- pika 一个nosql 存储, redis over rocksdb,非常需要人贡献代码胖友们, 感兴趣的欢迎加群294254078前来对线
新项目介绍/版本更新
- cppq 一个基于redis的任务队列
看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持!
C++ 中文周刊 第81期
从reddit/hackernews/lobsters/meetingcpp/知乎等等摘抄一些c++动态
弄了个qq频道,手机qq点击进入
欢迎投稿,推荐或自荐文章/软件/资源等
可以贴在下一期草稿里 草稿链接
2022 09 23
马上就国庆节了。节前节后这两周大概率没时间更新了。提前祝大家节日快乐。好好休息
资讯
标准委员会动态/ide/编译器信息放在这里
九月讨论汇总 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/#mailing2022-09
polymorphic_allocator<> instead of type-erasure
这个有点意思,众所周知,std::function用malloc,不能指定allocator,挺坑。如果std::function创建多了。malloc倒是成为瓶颈了。坑。
主要是加个 using allocator_type = std::pmr::polymorphic_allocator<>;
function_ref: a type-erased callable reference
这个讨论好久了。
Proxy: A Polymorphic Programming Library
这个也介绍过,是微软搞的类似folly::poly那种用户态的多态实现。
别的没啥说的。修修补补
编译器信息最新动态推荐关注hellogcc公众号 本周更新 2022-09-21 第168期
另外还有Azure CTO说c++不行了新项目转rust。c++老大说他喜新厌旧。具体大家搜一搜就好了。不贴出来了。有点八卦
问题在这里 https://www.zhihu.com/question/554750609
文章
这个分配器确实不错。差点项目里就用了。后来研究了一下发现checkpoint麻烦就放弃了。这个设计还是很有意思的
介绍防守编程的,_FORTIFY_SOURCE=3新级别,更严格,能抓到buffer overflow
被符号不匹配告警搞烦了
template <typename T>
bool has_repeated_values(const T& container) {
for (int i = 0; i < container.size() - 1; ++i) {
if (container[i] == container[i + 1]) return true;
}
return false;
}
// 修改后
std::vector vec = ...
if (!empty(vec)) {
for (int i = 1; i < ssize(vec); ...) {
...
}
}
内部帮你static_cast
。快谢谢库作者
看看见识见识
介绍各个平台系统api兼容性
介绍msvc告警的。没啥说的
不太懂windows下的汇编表现
看不太懂
视频
介绍cppfront herb大哥整的新活, cpp2。看个乐
这里有个介绍 https://www.zhihu.com/question/536288519/answer/2682665038
介绍range。没啥说的
开源项目需要人手
- asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群384042845和作者对线
- pika 一个nosql 存储, redis over rocksdb,非常需要人贡献代码胖友们, 感兴趣的欢迎加群294254078前来对线
新项目介绍/版本更新
- lager 一个UI框架,类似redux
工作招聘
哎。虾皮突发变动搞的人心惶惶啊。
看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持!
C++ 中文周刊 第80期
从reddit/hackernews/lobsters/meetingcpp知乎等等摘抄一些c++动态
弄了个qq频道,手机qq点击进入
欢迎投稿,推荐或自荐文章/软件/资源等
可以贴在下一期草稿里 草稿链接
2022 0916
资讯
标准委员会动态/ide/编译器信息放在这里
编译器信息最新动态推荐关注hellogcc公众号 2022-09-14 第167期
cppcon 2022还在进行中。视频放了几个。没看。有参与者说今年不太行。我去年还没怎么看。看视频信息量太低了。得看PPT然后找视频看。最近没啥空。周末有时间可以看看
文章
打印堆栈支持。看代码
#include <stacktrace>
#include <iostream>
int foo() {
std::cout << std::stacktrace::current();
return {};
}
int main() {
return foo();
}
可算支持了
各个编译器Modules支持的状态进展介绍
标准库的std::optional是不支持T&和void的。作者说了下自己可能会用到这个场景,标准库应该加上
之前介绍过。介绍了谷歌浏览器团队在解决指针问题的一些实践。实现了很多,但是有些文档不可见。
也介绍了其他方案的实现,比如这个unowned_ptr
代码难找就没有深入研究。不过讨论还是值得一看的。我后面整理一下
介绍Sanitizer
seastar代码走读
注意co_await的阻塞语义,永远超时导致不能使用
四舍五入等于c的东西。可以看个热闹
各种容器介绍,比如folly::fbvector, boost::small_vector等等。感兴趣的可以看看
写了个工具验证gcc产出的代码对不对,具体就是验证gimple IR。我不太懂。感兴趣可以看看
office团队要用module,这是msvc团队的一些探索
介绍msvc调试的。没用过不了解,有没有懂的给讲讲
代码长这样
struct WidgetTracker : IWidgetChangeNotificationSink
{
/* other stuff not relevant here */
/// IWidgetChangeNotificationSink
STDMETHODIMP OnCurrentWidgetChanged();
private:
WRL::ComPtr<IWidget> m_currentWidget;
std::mutex m_mutex;
};
HRESULT WidgetTracker::OnCurrentWidgetChanged()
{
auto guard = std::lock_guard(m_mutex);
RETURN_IF_FAILED(GetCurrentWidget(&m_currentWidget));
return S_OK;
}
OnCurrentWidgetChanged是个回调,如果m_currentWidget变了就会被调用
问题在于OnCurrentWidgetChanged被调用这个m_mutex锁住的瞬间,m_currentWidget析构,结果又触发一次OnCurrentWidgetChanged,导致死锁
这个问题根源在于锁锁不住comptr,无法避免递归调用。
解决方法也很简单,拷贝一份对象就行了。
HRESULT WidgetTracker::OnCurrentWidgetChanged()
{
WRL::ComPtr<IWidget> widget; // 可能别人在用它
auto guard = std::lock_guard(m_mutex);
RETURN_IF_FAILED(GetCurrentWidget(&widget));
m_currentWidget.Swap(widget);
return S_OK;
}
作者之前讨论过一段类似的代码,用的,shared_ptr, 也有相同的问题
class ThingManager
{
private:
std::mutex things_lock_;
std::vector<std::shared_ptr<Thing>> things_;
public:
void AddThing(std::shared_ptr<Thing> thing)
{
std::lock_guard guard(things_lock_);
things_.push_back(std::move(thing));
}
void RemoveThingById(int32_t id)
{
std::lock_guard guard(things_lock_);
auto it = std::find_if(things_.begin(), things_.end(),
[&](auto&& thing)
{
return thing->id() == id;
});
if (it != things_.end()) {
things_.erase(it);
}
}
};
class SuperThing : Thing
{
private:
ThingManager& manager_;
int32_t helper_id_ = 0;
public:
SuperThing(ThingManager& manager) :
manager_(manager)
{
auto helper = std::make_shared<Thing>();
helper_id_ = helper->id();
manager_.AddThing(helper);
}
~SuperThing()
{
manager_.RemoveThingById(helper_id_);
}
};
void test(ThingManager& manager)
{
auto s = std::make_shared<SuperThing>(manager);
auto id = s->id();
manager.AddThing(s);
s = nullptr; // 1
manager.RemoveThingById(id); // 2
}
问题是相同的,同一个锁被锁两次。如何触发?首先SuperThing会在2 这行真正的析构,1那行只会引用计数-1
RemoveThingById(id)是会锁的,内部触发了SuperThing析构,然后又调用了manager_.RemoveThingById(helper_id_);
,锁了同一个锁
你可能觉得这种代码写的有问题。这是锁和shared_ptr和坑爹析构三个同时引入引发的问题,我遇不到
解决方法也很简单。让这个shared_ptr活着,因为不知道哪个外部调用会用到这个shared_ptr
void RemoveThingById(int32_t id)
{
std::shared_ptr removed_thing; // 求求你活着
{
std::lock_guard guard(things_lock_);
auto it = std::find_if(things_.begin(), things_.end(), ...);
if (it != things_.end()) {
removed_thing = *it;
things_.erase(it);
}
}
}
引用计数指针和锁的问题。这里打个问号。要注意
oldnewthing的博客真精彩,总能遇到各种莫名其妙的bug
标准库提供了新函数来更友好的判断整数大小,不用自己写那些符号转换逻辑了
比如
template <class _Ty1, class _Ty2>
_NODISCARD constexpr bool cmp_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept {
static_assert(_Is_standard_integer<_Ty1> && _Is_standard_integer<_Ty2>,
"The integer comparison functions only "
"accept standard and extended integer types.");
if constexpr (is_signed_v<_Ty1> == is_signed_v<_Ty2>) {
return _Left == _Right;
} else if constexpr (is_signed_v<_Ty2>) {
return _Left == static_cast<make_unsigned_t<_Ty2>>(_Right) && _Right >= 0;
} else {
return static_cast<make_unsigned_t<_Ty1>>(_Left) == _Right && _Left >= 0;
}
}
VPSUBUSB z, x, y
VPMINUB z, x, y
VPCMPEQB w, z, x
我不太懂。不评价
用avx处理字符串中的斜杠,比如my title is \"La vie\"
通常写法
for (...) {
if ((*in == '\\') || (*in == '"')) {
*out++ = '\\';
}
*out++ = *in;
}
sse/avx写法
__m512i solidus = _mm512_set1_epi8('\\');
__m512i quote = _mm512_set1_epi8('"');
for (; in + 32 <= finalin; in += 32) {
__m256i input = _mm256_loadu_si256(in);
__m512i input1 = _mm512_cvtepu8_epi16(input);
__mmask64 is_solidus = _mm512_cmpeq_epi8_mask(input1, solidus);
__mmask64 is_quote = _mm512_cmpeq_epi8_mask(input1, quote);
__mmask64 is_quote_or_solidus = _kor_mask64(is_solidus, is_quote);
__mmask64 to_keep = _kor_mask64(is_quote_or_solidus, 0xaaaaaaaaaaaaaaaa);
__m512i shifted_input1 = _mm512_bslli_epi128(input1, 1);
__m512i escaped =
_mm512_mask_blend_epi8(is_quote_or_solidus, shifted_input1, solidus);
_mm512_mask_compressstoreu_epi8(out, to_keep, escaped);
out += _mm_popcnt_u64(_cvtmask64_u64(to_keep));
}
给我看困了
这段代码有bug,不懂这几个API的可能看不懂
bool ShuttingDown = false;
void MainThread()
{
DWORD id;
auto hThread = CreateThread(nullptr, 0, WorkerThread,
nullptr, 0, &id); // succeeds
BlahBlahBlah(); // do useful work
// Time to clean up. Post an APC to the worker thread
// to tell it that it's time to go home.
QueueUserAPC(WakeWorker, hThread, 0); // succeeds
WaitForSingleObject(hThread, INFINITE); // hangs
CloseHandle(hThread);
}
void CALLBACK WakeWorker(ULONG_PTR)
{
ShuttingDown = true;
}
DWORD CALLBACK WorkerThread(void*)
{
// Do work until shut down.
do
{
// All work is posted via APCs.
SleepEx(INFINITE, TRUE);
} while (!ShuttingDown);
return 0;
}
简单来说SleepEx通过QueueUserAPC来唤醒,有一种场景,QueueUserAPC唤醒了,但是CreateThread执行的慢,导致SleepEx没收到通知,从而永远死锁
怎么解决这个问题?把do while循环改成while就行了。因为这种场景已经shutdown了,不应该执行sleep
DWORD CALLBACK WorkerThread(void*)
{
// Do work until shut down.
while (!ShuttingDown)
{
// All work is posted via APCs.
SleepEx(INFINITE, TRUE);
}
return 0;
}
- Serializing asynchronous operations in C++/WinRT
- Creating a manual-start C++/WinRT coroutine from an eager-start one, part 1
- Creating a lazy-start C++/WinRT coroutine from an eager-start one, part 2
讲协程的。说来惭愧我还不是很懂。就不介绍了
视频
- [C++ Weekly - E...
C++ 中文周刊 第79期
从reddit/hackernews/lobsters/purecpp知乎等等摘抄一些c++动态
弄了个qq频道,手机qq点击进入
欢迎投稿,推荐或自荐文章/软件/资源等
可以贴在下一期草稿里 草稿链接
2022 0908 提前发了。周五有事
资讯
标准委员会动态/ide/编译器信息放在这里
llvm 15发布了。c++20支持等等一大堆修改
编译器信息最新动态推荐关注hellogcc公众号 本周更新 2022-09-07 第166期
文章
-
最新全面hashmap 评,其中emhash7/8 是知乎网友ktrpime 实现的的,性能排名还不错。感兴趣的可以研究下
要注意协程被线程切换影响
c++23详细总结
c++20应用协程举例
std::function的分析
seastar的一些代码走读。可以看看
其实就是编译期检测接口的能力
一组协程教程
#include <ranges>
template <auto Begin, auto End, auto List>
auto slice = List
| std::ranges::views::drop(Begin)
| std::ranges::views::take(End);
static_assert(
slice<1_c, 2_c, boost::mp::list<int, double, float>()>
==
boost::mp::list<double, float>());
typelist实现更简单了。恐怖
optinal的move并不会真正的move T, 让T为无效value
template <typename T>
void test(T v)
{
optional<T> o = v;
assert (o); // o contains a value
optional<T> p = std::move(o);
assert (o); // o still contains a value!
}
完全取决于T的move,optional会保留T的壳子。
比如unique_ptr
int * p = new int(1);
optional<unique_ptr<int>> op = unique_ptr<int>(p);
assert (op);
assert (op->get() == p);
optional<unique_ptr<int>> o2 = std::move(op);
assert (op); // still contains a value
assert (op->get() == nullptr); // the value is moved from
assert (o2);
assert (o2->get() == p);
unique_ptr内部会搬走,但本身是还在optional里的。这样实现更快,对于trival类型,这样优化的可以直接memcpy,更高效一些
大概实现成这个样子
template <typename Trivial>
class optional<Trivial>
{
bool _is_initialized;
Trivial _value;
optional() : _is_initialized(false) {}
// use defaulted copy and move
};
// Second argument is a pointer to the type of std::fclose, we could also have
// written it out explicitly as std::unique_ptr<FILE, int (*)(FILE*)>.
using FilePtr = std::unique_ptr<FILE, decltype(std::fclose) *>;
// Instantiate the FILE* with the destructor we want.
FilePtr file(std::fopen(filename, "rbe"), std::fclose);
// Do stuff with the file
std::fread(buf_.data(), 1, buf_.size(), file.get());
比写个deferGuard能更干净些
using XXH3StatePtr = std::unique_ptr<XXH3_state_t, decltype(XX3_freeState) *>;
XXH3StatePtr state(XXH3_createState(), XXh3_freeState);
但这种写法的问题在于,需要判定创建失败/指针是否有效
一个简单的函数
[[nodiscard]] auto say_a_to(
std::string_view what, std::string_view name) -> std::string {
return std::string{what} + ", " + std::string{name} + "!";
}
say_a_to("Hi", "Kate"); // -> "Hi, Kate!"
struct {
[[nodiscard]] auto operator()(
std::string_view what, std::string_view name) const -> std::string {
return std::string{what} + ", " + std::string{name} + "!";
}
} say_b_to;
say_b_to("Hello", "Tony"); // -> "Hello, Tony!"
没啥新奇的,但是c++23支持多维数组了,所以说operator[]
也算是一种函数了
比如
struct {
[[nodiscard]] auto operator[](
std::string_view what, std::string_view name) const -> std::string {
return std::string{what} + ", " + std::string{name} + "!";
}
} say_d_to;
say_d_to["Goodbye", "Tony"]; // -> "Goodbye, Tony!"
struct {
template <std::integral ...Ts>
[[nodiscard]] auto operator[](Ts... ts) const noexcept {
return (0 + ... + ts);
}
} sum;
const auto s1 = sum[1, 2, 3]; // 6
const auto s2 = sum[]; // 0
我只能说看个乐,别这么写求求了
视频
#include <iostream>
#include <ranges>
#include <string_view>
int main() {
// this is lazily evaluated
auto strings = std::string_view{"Hello C++ 20"} | std::views::split(' ');
// the result is a range of ranges
for (const auto &ref : strings) {
// C++ 20
std::cout << '\n' << std::string_view{ref.begin(), ref.end()};
// C++ 23
std::cout << '\n' << std::string_view{ref};
}
}
开源项目需要人手
- asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群384042845和作者对线
- pika 一个nosql 存储, redis over rocksdb,非常需要人贡献代码胖友们, 感兴趣的欢迎加群294254078前来对线
工作招聘
寒冬了
华为出新手机了,但我不买,因为想把寒冬传给它
看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持!
C++ 中文周刊 第78期
从reddit/hackernews/lobsters/meetingcpp摘抄一些c++动态
弄了个qq频道,手机qq点击进入
欢迎投稿,推荐或自荐文章/软件/资源等
可以贴在下一期草稿里 草稿链接
2020 09 02
资讯
标准委员会动态/ide/编译器信息放在这里
编译器信息最新动态推荐关注hellogcc公众号 本周更新 2022-08-31 第165期
文章
尽量让move 构造函数 noexcept, 不然用vector可能有问题,多copy
比如这个
struct Instrument {
int n_;
std::string s_;
Instrument(const Instrument&) = default;
// WRONG!!
Instrument(Instrument&& rhs)
: n_(std::exchange(rhs.n_, 0)),
s_(std::move(s_))
{}
// RIGHT!!
Instrument(Instrument&& rhs) noexcept
: n_(std::exchange(rhs.n_, 0)),
s_(std::move(s_))
{}
};
如果不是noexcept,vector的move判定内部的T不是is_nothrow_move_constructible, 那就构造复制一份,所以多了个拷贝。也就是博主说的vector pessimization问题
vector本身的搬迁move的多余动作,如果能nothrow,move就更简单
free没有size看上去是个巧妙的设计,实际上隐含了挺多脏活
协程背后都做了啥
有点意思
struct foo {
[[nodiscard]] foo(auto& resource) {}
};
struct [[nodiscard]] bar {};
auto fn() -> bar;
[[nodiscard]] auto fn2() -> bool;
int main(int, char** argv){
foo{argv}; // ignoring temp created by [[nodiscard]]
fn(); // ignoring return value with [[nodiscard]]
fn2(); // ignoring return value with [[nodiscard]]
}
老文,科普一下概念。
参数不是constexpr
consteval auto square(int x) -> int { return x * x; }
constexpr auto twice_square(int x) -> int { return square(x); }
编译不过。作者展示了一下编译期计算。哎又回去了。constexpr还是不够const
看个乐
介绍这几个flag
-march=native
肯定接触过吧
茴香豆的茴的20种写法
为什么大哥解bug这么熟练
视频
static constexpr 和 inline constexpr区别。inline constexpr能合并文件重复的数据,是文件级别,static是函数级别,并不能合并代码段
聪明的你想到了static inline constexpr。这个效果就是static constexpr。static限制了范围
开源项目需要人手
- asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群384042845和作者对线
- pika 一个nosql 存储, redis over rocksdb,非常需要人贡献代码胖友们, 感兴趣的欢迎加群294254078前来对线
工作招聘
寒冬了
看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持!
C++ 中文周刊 第77期
从reddit/hackernews/lobsters/meetingcpp摘抄一些c++动态
弄了个qq频道,手机qq点击进入
欢迎投稿,推荐或自荐文章/软件/资源等
可以贴在下一期草稿里 链接
2022 08 26
准备做视频,目前有代码走读/benchmark俩主题,大家还有啥意见可以供稿一下。下一期视频准备先来个userver代码走读。我先准备准备材料
资讯
标准委员会动态/ide/编译器信息放在这里
标准委员会八月邮件 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/#mailing2022-08
编译器信息最新动态推荐关注hellogcc公众号 本周更新 2022-08-24 第164期
文章
namespace detail {
template <bool> struct conditional;
template <> struct conditional<false> {
template <class, class T> using fn = T;
};
template <> struct conditional<true> {
template <class T, class> using fn = T;
};
} // namespace detail
template <bool B, typename T, typename F>
using conditional_t = typename detail::conditional<B>::template fn<T, F>;
说实话,没看懂
关于协程的封装探索,挺有意思的
看不懂
GCC整体介绍
看个乐
fmt对输出有严格限制
介绍他自己写的静态检查器 https://github.com/GregUtas/robust-services-core
main函数执行前都干了啥,填充入参之类的
#include <iostream>
#include <string>
#include <stdlib.h>
std::string message;
extern "C" {
void __asan_on_error() {
std::cout << "You caused an error: " << message << std::endl;
}
}
int main() {
int array[8];
for(int k = 0;; k++) {
message = std::string("access at ") + std::to_string(k);
array[k] = 0;
}
return EXIT_SUCCESS;
}
不过不太实用
memcmp比较出错。qemu这种牛逼软件也会有这种问题啊
int arr[10000]; // 一个已有的数组
mdspan mdarr{arr, i, j, k}; // 把已有的数组 arr 视作 i×j×k 的多维数组
int value = mdarr[x, y, z]; // 访问多维数组的元素
mdarr[x, y, z] = 42; // 向元素赋值
简而言之就是这几行。现在你已经学会了
视频
-ftime-trace 生成编译的时间json结果,可以用chrome://tracing/ 浏览器来加载查看。可以自己试一试
觉得麻烦想快速验证的,这里有个build bench https://build-bench.com/b/zLopSp4Yj5XaijZSuwqiYvA2PNU
开源项目需要人手
- asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群384042845和作者对线
- pika 一个nosql 存储, redis over rocksdb,非常需要人贡献代码胖友们, 感兴趣的欢迎加群294254078前来对线
新项目介绍/版本更新
- cake 一个c写的c前端
- asyncgi 一个fastcgi库
- MultiArena 内存分配器实现,O1,定位类似pmr
工作招聘
突然寒冬卧槽了,任正非你这嘴开过光吧
看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持!