Skip to content

Latest commit

 

History

History
658 lines (474 loc) · 18.9 KB

126.md

File metadata and controls

658 lines (474 loc) · 18.9 KB
layout title
post
第126期

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还没开发多久,就维护状态了,又引入了新库 https://github.com/microsoft/wil/

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

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

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();

介绍链表实现,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构造可能抛异常

基本结构就这样

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;
};

介绍几种高效的用法,比如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);

另外评论区@star-hangxing 指出,可以用 unique_ptr<int[]>(new int[count])

感谢

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) 
    );
}
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; }() || ...);
}

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

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

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

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

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

可以这样理解

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一条新的数组,然后管理数组间的串联关系

列举各种实现的差异

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
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

看晕了

老文章,看代码

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 指正,这里表示感谢

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

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

#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
}

想让对象通过特别的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
}

无栈协程各种缺点

  • 参数生命周期问题
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));
}

同样的崩溃问题又出现了

  • 测试问题,怎么避免上面这种用法的出现?await_transform?

  • 性能问题,内存分配的浪费?即使有HALO优化,还是会有很大的栈空间浪费?

  • 还是性能问题,生成的函数太多了,debug build很多浪费,这个无解,只能等编译器优化

视频

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

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

开源项目需要人手

  • asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群753302367和作者对线
  • Unilang deepin的一个通用编程语言,点子有点意思,也缺人,感兴趣的可以github讨论区或者deepin论坛看一看。这里也挂着长期推荐了
  • gcc-mcf 懂的都懂

新项目介绍/版本更新

工作招聘

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


本文永久链接

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