C++ 中文周刊 第126期

周刊项目地址

公众号

RSS https://github.com/wanghenshui/cppweeklynews/releases.atom

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

提交 issue

感谢不语赞助


资讯

标准委员会动态/ide/编译器信息放在这里

编译器信息最新动态推荐关注hellogcc公众号 本周更新 2023-08-09 第214期

boost::async review https://github.com/klemens-morgenstern/async

https://www.reddit.com/r/cpp/comments/15l8xju/review_of_proposed_boostasync_begins/

比较集成在ASIO上

文章

cppwinrt is in maintenance mode

感觉cppwinrt还没开发多久,就维护状态了,又引入了新库 https://github.com/microsoft/wil/

说实话,微软总搞这种事,开发一个新技术,让大伙学,还没怎么学明白,换坑,再重新学,和google有一拼

之前更新了很多winrt的文章,得亏一个没看,他妈的,纯纯浪费生命,微软我___

Did you know that C++26 added ‘A nice placeholder with no name’

auto foo() { return 42; }

int main() {
    auto unused = foo(); // warning in C++23
    auto _ = foo();      // no warning in C++26
}

话说,我以前是这么玩的, std::ignore

decltype(std::ignore) _;
_ = blahblah();

Inside STL: The lists

介绍链表实现,MSVC和gcc/clang的实现还不太一样, 单向链表都差不多

template<typename T>
struct forward_list {
    forward_list_node<T>* head;
};

template<typename T>
struct forward_list_node {
    forward_list_node<T>* next;
    T value;
};

双向链表,有个边界判定问题

template<typename T>
struct list {
    list_node_base<T> head; // or "list_node_base<T>* head;"
    size_t size;
};

template<typename T>
struct list_node_base {
    list_node<T>* next;
    list_node<T>* prev;
};

template<typename T>
struct list_node : list_node_base<T> {
    T value;
};

可以有个dummy node来管理开头结尾,也可不用,多一个内存分配,这也是msvc需要注意的地方,list构造可能抛异常

Inside STL: The map, set, multimap, and multiset

基本结构就这样

struct tree {
    node_base header; // or "node_base* header;"
    size_t size;
};

struct node_base {
    node_base* parent;
    node_base* left;
    node_base* right;
};

struct node : node_base {
    bool color; // red or black
    payload data;
};

Five Advanced Initialization Techniques in C++: From reserve() to piecewise_construct and More.

介绍几种高效的用法,比如vector reserve + emplace_back,这种大家都知道,代码就不贴了

lambda 捕获优化

auto result = std::find_if(vs.begin(), vs.end(),
        [&prefix](const std::string& s) {
            return s == prefix + "bar"s; 
        }
    );

这种使用捕获用法,每次都要生成对象,效率低下,需要改成

result = std::find_if(vs.begin(), vs.end(), 
        [savedString = prefix + "bar"s](const std::string& s) { 
            return s == savedString; 
        }
    );

这样,避免每次lambda都构造

unique_ptr优化 make_unique_for_overwrite,有时候你想用unique_ptr管理 buffer,但没有必要清零,可以用这个接口,类似stringresize_for_overwrite

auto ptr = std::make_unique_for_overwrite<int[]>(1000);

pair/tuple优化 piecewise_construct forward_as_tuple

// 1
std::cout << "regular: \n";
std::pair<MyType, MyType> p { MyType{"one", 1}, MyType{"two", 2}};

// 2
std::cout << "piecewise + forward: \n";
std::pair<MyType, MyType>p2(std::piecewise_construct,
            std::forward_as_tuple("one", 1),
            std::forward_as_tuple("two", 2));

起到一个就地构造的效果,类似emplace_back

对于map来说,这种也能用

#include <string>
#include <map>

struct Key {
    Key(int a, int b) : sum(a + b) {}
    int sum;
    bool operator<(const Key& other) const { 
        return sum < other.sum; 
    }
};

struct Value {
    Value(const std::string& s, double d) : name(s), data(d) {}
    std::string name;
    double data;
};

int main() {
    std::map<Key, Value> myMap;

    // doesn't compile: ambiguous
    // myMap.emplace(3, 4, "example", 42.0);

    // works:
    myMap.emplace(
        std::piecewise_construct,
        std::forward_as_tuple(3, 4),  
        std::forward_as_tuple("example", 42.0) 
    );
}

Fun with quadratic pack-expansions

consteval bool all_of(const auto& f, const auto&... xs) {
  return (f(xs) && ...);
}

consteval bool contains(const auto& n, const auto&... hs) {
  return ((hs == n) || ...);
}

consteval int count(const auto& n, const auto&... hs) {
  return (0 + ... + (hs == n));
}

static_assert(count(1, 3,1,4,1,6) == 2);
static_assert(count(2, 3,1,4,1,6) == 0);

这都很常规

类似的,可以写个判断重复

consteval bool has_any_duplicates() { return false; }
consteval bool has_any_duplicates(const auto& n, const auto&... hs) {
  return ((n == hs) || ...) || has_any_duplicates(hs...);
}

static_assert(has_any_duplicate(3,1,4,1,6));
static_assert(!has_any_duplicate(2,7,1,8,3));
static_assert(has_any_duplicate(9,9,9));
static_assert(!has_any_duplicate(9));
static_assert(!has_any_duplicate());

有点递归了,感觉和count有点像?用count改写

constexpr auto has_duplicate_of(const auto& value) {
  return [&](const auto&... hs) {
    return count(value, hs...) >= 2;
  };
}
consteval bool has_any_duplicates(const auto&... hs) {
  return (has_duplicate_of(hs)(hs...) || ...);
}

再简化一下,不用 has_duplicate_of

consteval bool has_any_duplicates(const auto&... hs) {
  return ((count(hs, hs...) >= 2) || ...);
}

不用count再简化一下

consteval bool has_any_duplicates(const auto&... hs) {
  return ([&](const auto& n) { return (0 + ... + (hs == n)) >= 2; }(hs) || ...);
}

还有优化空间! 参数n可以优化

consteval bool has_any_duplicates(const auto&... hs) {
  return ([&,&n=hs]{ return (0 + ... + (hs == n)) >= 2; }() || ...);
}

A case in optimizing auto-vectorized code

手把手教你写SIMD代码。看晕了 代码在这里https://github.com/oliora/habr-switches-perf-test

Inside STL: The unordered_map, unordered_set, unordered_multimap, and unordered_multiset

hashtable的内存结构,基本就这样

struct hashtable{
    using hint = std::list<payload>::iterator;

    std::list<payload> list;
    std::vector<hint> buckets;
};

开链在现在的硬件看来已经证明是缓存不友好的了,flatmap可以解决这一点

Inside STL: The deque, design

可以这样理解

template<typename T>
struct simple_deque {
    T* elements;
    T* first;
    T* last;
    size_t capacity;
};

/*
                           first    last
elements | ? |  ? | ? | ? | 1 | 2 | 3 | ? |

size = last - first  =3

cap = 8
*/

大概就是这样的结构 从中间往外展开,如果空间用光,就分配alloc一条新的数组,然后管理数组间的串联关系

Inside STL: The deque, implementation

列举各种实现的差异

| | gcc | clang | msvc | | —————————– | ———————————————————————————- | —————————————————————————————————– | ——————————————————————- | | Block size | as many as fit in 512 bytes but at least 1 element | as many as fit in 4096 bytes but at least 16 elements | power of 2 that fits in 16 bytes but at least 1 element | | Initial map size | 8 | 2 | 8 | | Map growth | 2× | 2× | 2× | | Map shrinkage | On request | On request | On request | | Initial first/last | Center | Start | Start | | Members | block** map; size_t map_size; iterator first; iterator last; | block** map; block** first_block; block** last_block; block** end_block; size_t first; size_t size; | block** map; size_t map_size; size_t first; size_t size; | | Map layout | counted array | simple_deque | counted array | | Valid range | Pair of iterators | Start and count | Start and count | | Iterator | T* current; T* current_block_begin; T* current_block_end; block** current_block; | T* current; block** current_block; | deque* parent; size_t index; | | begin()/ end() | Copyfirstandlast. | Breakfirstandfirst + sizeinto block index and offset. | Breakfirstandfirst + sizeinto block index and offset. | | Spare blocks | Aggressively pruned | Keep one on each end | Keep all |

On the optimal bounds for integer division by constants

看晕了

C++20 Dynamic Allocations at Compile-time

老文章,看代码

consteval auto as_constant(auto value) {
    return value;
}
constexpr int Calc(int x) {  return 4 * x; }
//consteval int Calc(int x) {  return 4 * x; }
int main() {
    auto res = Calc(2); 
    // auto res = as_constant(Calc(2)); 
    ++res;  
    res = Calc(res); //编译不过
    return res;
  }

如果改成as_constant就能编译过。as_constant看起来傻逼,但是强制编译期计算了

之前代码抄错 #68 @fanenr 指正,这里表示感谢

Making your own array

造轮子,没觉得有啥意思 代码这里 https://github.com/PipeRift/pipe/blob/feature/custom-arrays/Include/Pipe/PipeArrays.h

C++23: multidimensional operator[]

看代码, 多维数组访问下标

#include <vector>
#include <https://raw.githubusercontent.com/kokkos/mdspan/single-header/mdspan.hpp>
#include <iostream>

int main()
{
  std::vector v = {1,2,3,4,5,6,7,8,9,10,11,12};

  // View data as contiguous memory representing 2 rows of 6 ints each
  auto multispan = std::experimental::mdspan(v.data(), 2, 6);
  std::cout << multispan[0, 1] << '\n'; // 2
}

Passkey Idiom: A Useful Empty Class

想让对象通过特别的factory来构造。自己不能构造

看代码。没啥说的,就是private + tag类限制

tag类甚至可以不用

class Secret {
  class ConstructorKey {
    friend class SecretFactory;
  private:
    ConstructorKey() {};
    ConstructorKey(ConstructorKey const&) 
      = default;
  };
public:
  //Whoever can provide a key has access:
  explicit Secret(std::string str,
  ConstructorKey) : data(std::move(str)) {}
private:
  //these stay private, since Secret itself has
  // no friends any more
  void addData(std::string const& moreData);
  std::string data;
};
class SecretFactory {
public:
  Secret getSecret(std::string str) {
    return Secret{std::move(str), {}}; 
  }
  // void modify(Secret& secret, 
  // std::string const& additionalData) {
  //   secret.addData(additionalData);   //ERROR:
  //       // void Secret::addData(const string&)
  //                                // is private
  // }
};
int main() {
  Secret s{"foo?", {}};    //ERROR:
  // Secret::ConstructorKey::ConstructorKey()
  // is private
  SecretFactory sf;
  Secret s = sf.getSecret("moo!"); //OK
}

The downsides of C++ Coroutines

无栈协程各种缺点

task<void> async_insert(T && val);

task<void> async_find(const T &);

task<void> async_write(span<byte>);

这种不注意生命周期一不留神就用错

task<void> send_all(string s) {
  for (auto & source : m_sources)
  {
    co_await source.send(s);
  }
}

看上去没问题,如果m_sources有改动,就完了

所以复制一下是不是就没问题了?

task<void> send_all(string s)
{
  std::vector<task<void>> sends;
  sends.reserve(m_sources.size());
  for (auto & source : m_sources)
  {
    sends.push_back(source->send(s));
  }

  co_await wait_all( sends.begin(), sends.end() );
}

等一下,m_sources如果析构了呢?

保活一下

task<void> send_all(string s)
{
  std::vector<task<void>> sends;
  std::vector<shared_ptr<Source>> sources = m_sources;
  sends.reserve(sources.size());
  for (auto & source : sources)
  {
    sends.push_back(source->send(s));
  }

  co_await wait_all( sends.begin(), sends.end() );
}

或者有个爹

struct Source
{
  std::vector<std::coroutine_handle> dependent_coroutines;
  ~Source()
  {
    for (auto & coroutine : dependent_coroutines)
      coro.destroy();
  }

  task<void> send(string s)
  {
    auto corohandle = co_await get_current_coroutine{};
    dependent_coroutines.push_back( corohandle );
    ...
    dependent_coroutines.erase( dependent_coroutines.find( corohandle ) );
  }
}

或者加个锁?

task<void> send_all(string s)
{
  co_await m_sourcesLock.read_lock();
  std::vector<task<void>> sends;
  sends.reserve(m_sources.size());
  for (auto & source : m_sources)
  {
    sends.push_back(source->send(s));
  }

  co_await wait_all( sends.begin(), sends.end() );
  co_await m_sourcesLock.read_unlock();
}

已经想吐了

co_await fetch_data("key");

这代码的问题相信你能看出来 key的生命周期是临时的。如果立即执行这个协程,不会崩,你显然没发现这个bug

然后这种代码越来越多,突然某一天就崩了,你还在纳闷咋回事

哦你发现了,想了想,保存一下,延长一下生命周期,应该行了吧

eager_task<string> fetch_data(string_view key)
{
  auto it = cache.find(key)
  if (it != cache.end())
  {
    return it->second;
  }

  auto data = co_await fetch_remote(key);
  cache.emplace(key, data);
}

但是指不定某个兄弟就这么写了

eager_task<void> fetch_mydata(string_view key)
{
  return fetch_data(std::format("mysystem/{}", key));
}

同样的崩溃问题又出现了

视频

讲的玩意类似taskflow,但是是用hash来串联类型依赖。没有源代码

讲函数怎么写assert写约束写断言,还算有点意思,周末整理一下这个

开源项目需要人手

新项目介绍/版本更新

工作招聘

有没有数据库相关的工作推荐我一下,我要失业了快


本文永久链接

如果有疑问评论最好在上面链接到评论区里评论,这样方便搜索,微信公众号有点封闭/知乎吞评论

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