C++ 中文周刊 第3期

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

每周更新

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

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


资讯

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

本周周报github直达

文章

正常来说有RVO优化 return std::move(s);属于非常多余的动作,会禁止RVO

但是某些场景下,RVO可能有害,也就是逃逸分析(Escape analysis)

作者举了个例子,这个代码段https://godbolt.org/z/jG7x5h 解释了编译器如何分析逃逸,以及这种场景下,无法RVO优化,所以RVO的汇编反而比禁止的要好

也解释了为什么gcc和clang效果不同 -> 在g++中 (x)默认表示禁止RVO,这是一个坑 SO

这篇文章还列了相关的提案,以及自己的建议PATCH,非常建议阅读,对于语言律师识别坑有帮助

自己实现了一个Error类。类似rocksdb的status

结论,除法要比乘慢几倍,编译器有时候能做优化,把除改成乘,比如/2变成*0.5 可以设定-freciprocal-math总是让编译器做优化

复习一下折叠表达式 比如这段代码 向右折叠,但是会有问题,比如sum()空的

template<typename... Values>
auto sum(Values const&... values)
{
    return (values + ...);
}

可以改成向左边折叠

template<typename... Values>
auto sum(Values const&... values)
{
    return (0 + ... + values);
}

类似rust的模式匹配语法,很有意思 应该c++23会上

template<auto...> struct ids{};

template<auto N, auto... Ns>
auto dispatch(auto value, ids<N, Ns...>) -> decltype(value) {
  return inspect (value) {
    N  => value;
    _  => [] {
      if constexpr (sizeof...(Ns) > 0) {
        return dispatch(value, ids<Ns...>{});
      } else {
        return {};
      }
    }()
  };
}

int main() {
  std::cout << dispatch(0, ids<1, 2, 3>{}); // prints 0
  std::cout << dispatch(4, ids<1, 2, 3>{}); // prints 0

  std::cout << dispatch(1, ids<1, 2, 3>{}); // prints 1
  std::cout << dispatch(2, ids<1, 2, 3>{}); // prints 2
  std::cout << dispatch(3, ids<1, 2, 3>{}); // prints 3
}

这里讲了一个技巧,方便mock singleton

template<class T>
struct singleton {
  static auto& get() {
    static T s{};
    return s;
  }
};

class api {
 public:
  virtual ~api() = default;
  virtual auto call() const -> int { return 42; }
};

class app {
 public:
  auto run() -> int {
    return singleton<api>::get().call();
  }
};

class app_di {
 public:
  constexpr explicit(true) app_di(const api& api)
    : api_{api}
  { }

  auto run() const -> int {
    return api_.call();
  }

 private:
  const api& api_;
};

int main() {
  {
    app a{}; // coupled
    assert(42 == a.run());
  }

  {
    app_di a{singleton<api>::get()}; // injected
    assert(42 == a.run())
  }

  {
    struct : api {
      auto call() const -> int override { return 43; }
    } fake_api{};

    app_di api{fake_api}; // faked
    assert(43 == api.run());
  }
}

通过di这个类保持相同的结构,实现不同的api接口塞进去就行了

实际上我见到的大多是一个全局的getter/setter,来实现类似的功能,setter放mock的接口类

没什么新鲜的

作者写了个cp工具,用上了io uring和多线程拷贝,要比cp快,值得看一看代码

视频

设计了几种mutex guard,代码仓库 https://github.com/copperspice/cs_libguarded

几个点

std::mutex设置成mutable这样getter可以const

正常的写法可能就是

int data;
mutable std::mutex mtx;
//////
int getter() const {
  std::lock_guard l(mtx);
  return data;
}

作者设计了一个封装,结合lock_guard和访问于一身

plain_guarded<int> data;
////
int getter() const {
  auto p = data.lock();
  return *p;
}

同理,std::shared_mutex对应一个shared_guarded,封装好的类有更好的编译器检查,

如果直接用的mutex,mutex和data的对应关系不明显,可能需要编译器提供GUARD_BY来帮助处理

shared_guarded<int> data;
////
int getter() const {
  auto p = data.lock_shared(); //这里的p是const的
  return *p;
}

更进一步,把read /write的影响错开,lr_guarded,读写不影响,内部维护两份副本(对于小类型可以,自定义的,可能拷贝代价太大)

读不影响写写不影响读,速度快,这种场景就是RCU了,这里实现了一个rcu_listhttps://github.com/copperspice/cs_libguarded/blob/fcc67e3503a28591532f01476bba5076ffaf272d/src/cs_rcu_list.h

就是这段代码,s1和s2是不一样的

#include <iostream>
#include <string>
int main()
{
  std::string str = "hello world";
  std::string s1{str,3}; //lo world
  std::string s2{"hello world",3}; //hel
  std::cout<<s1<<'\n';
  std::cout<<s2<<'\n';
}

构造函数重载的问题

第一个匹配了

basic_string( const basic_string& other,
              size_type pos,
              size_type count = std::basic_string::npos,
              const Allocator& alloc = Allocator() );

默认count等于结尾

一个匹配了

basic_string( const CharT* s,
              size_type count,
              const Allocator& alloc = Allocator() );

count是3

string的构造函数太多太坑爹了


本文永久链接

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