Skip to content

Latest commit

 

History

History
327 lines (224 loc) · 9.38 KB

153.md

File metadata and controls

327 lines (224 loc) · 9.38 KB
layout title
post
第153期

C++ 中文周刊 2024-03-30 第153期

周刊项目地址

公众号

qq群 点击进入

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

欢迎投稿,推荐或自荐文章/软件/资源等,评论区留言

本期文章由 HNY 赞助

最近博客内容较少,所以基本整合起来发


资讯

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

编译器信息最新动态推荐关注hellogcc公众号 本周更新 2024-03-20 第246期

最近的热门事件无疑是xz被植入后门了,埋木马的哥们主动参与社区贡献,骗取信任拿到直接commit权限

趁主要维护人休假期间埋木马,但是木马有问题回归测试被安全人员发现sshd CPU升高,找到xz是罪魁祸首

无间道搁这

文章

std::ignore 或者 decltype(std::ignore) _; 然后用 _,或者不自己写,等c++26

resize 的参数为负数会异常(比如参数溢出意外负数)

有句讲句,标准库里的异常有时候很奇怪,大动干戈,副作用还是异常应该有明显的区分。但是目前来看显然是一股脑全异常了

比如stoi异常,这些场景里expect<T>更合适,或者c传统的返回值处理更合理一些

科普

告警更明显一些

gcc14加了个-fanalyzer

包括上面的buffer溢出分析,死循环分析,比如 https://godbolt.org/z/vn55nn43z

void test (int m, int n) {
  float arr[m][n];
  for (int i = 0; i < m; i++)
    for (int j = 0; j < n; i++)
      arr[i][j] = 0.f;
  /* etc */
}

这里里面的循环条件一直没变,所以一直是死的

编译器能分析出问题

<source>: In function 'test':
<source>:5:23: warning: infinite loop [CWE-835] [-Wanalyzer-infinite-loop]
    5 |     for (int j = 0; j < n; i++)
      |                     ~~^~~
  'test': events 1-5
    |
    |    5 |     for (int j = 0; j < n; i++)
    |      |                     ~~^~~  ~~~
    |      |                       |     |
    |      |                       |     (4) looping back...
    |      |                       (1) infinite loop here
    |      |                       (2) when 'j < n': always following 'true' branch...
    |      |                       (5) ...to here
    |    6 |       arr[i][j] = 0.f;
    |      |       ~~~~~~~~~        
    |      |             |
    |      |             (3) ...to here
    |
ASM generation compiler returned: 0
<source>: In function 'test':
<source>:5:23: warning: infinite loop [CWE-835] [-Wanalyzer-infinite-loop]
    5 |     for (int j = 0; j < n; i++)
      |                     ~~^~~
  'test': events 1-5
    |
    |    5 |     for (int j = 0; j < n; i++)
    |      |                     ~~^~~  ~~~
    |      |                       |     |
    |      |                       |     (4) looping back...
    |      |                       (1) infinite loop here
    |      |                       (2) when 'j < n': always following 'true' branch...
    |      |                       (5) ...to here
    |    6 |       arr[i][j] = 0.f;
    |      |       ~~~~~~~~~        
    |      |             |
    |      |             (3) ...to here
    |
Execution build compiler returned: 0
Program returned: 255

这个功能非常有用 gcc14已经发布,能体验到赶紧用起来,免费的静态检查了属于是

range的问题,如果range 的顺序颠倒,可能会产生未定义行为

举个例子,正常的范围使用

std::set<int> x=...;

// elements in [a,b]
auto first = x.lower_bound(a);
auto last  = x.upper_bound(b);
 
while(first != last) std::cout<< *first++ <<" ";

// elements in [a,b)
auto first = x.lower_bound(a);
auto last  = x.lower_bound(b);

// elements in (a,b]
auto first = x.upper_bound(a);
auto last  = x.upper_bound(b);

// elements in (a,b)
auto first = x.upper_bound(a);
auto last  = x.lower_bound(b);

这里的用法的潜在条件是a < b,如果不满足,就完蛋了, 似乎没有办法预防写错,手动assert?

这也容易引起错误,能不能让使用者不要用接口有隐形成本?

boost multiindex设计了一种接口

template<typename LowerBounder,typename UpperBounder>
std::pair<iterator,iterator>
range(LowerBounder lower, UpperBounder upper);

显然,不同的类型,隐含一层检查,看上去不好用,但是结合boost lambda2,非常直观

// equivalent to std::set<int>
boost::multi_index_container<int> x=...;

using namespace boost::lambda2;

// [a,b]
auto [first, last] = x.range(_1 >= a, _1 <= b);

// [a,b)
auto [first, last] = x.range(_1 >= a, _1 < b);

// (a,b]
auto [first, last] = x.range(_1 > a,  _1 <= b);

// (a,b)
auto [first, last] = x.range(_1 > a,  _1 < b);

倾向于返回range处理,而不是手动拿到range,即使出现a>b的场景,顶多返回空range

这样要比上面的用法更安全一些

唉,API设计的问题还是有很多需要关注的地方

幽默代码一例(别这么写)

#include <iostream>
 
template<class T>
struct larrow {
    larrow(T* a_) : a(a_) { }
    T* a;
};
 
template <class T, class R>
R operator<(R (T::* f)(), larrow<T> it) {
    return (it.a->*f)();
}
 
template<class T>
larrow<T> operator-(T& a) {
    return larrow<T>(&a);
}
 
struct C {
    void f() { std::cout << "foo\n"; }    
};
 
int main() {
    C x;
    (&C::f)<-x;
}

TLDR enum没指定默认值的bug,类似int不指定默认值

一个例子

template<class T>
struct S {
    S() requires (sizeof(T) > 3) = default;
    S() requires (sizeof(T) < 5) = default;
};

static_assert(std::is_trivial_v<S<int>>);
static_assert(not std::is_default_constructible_v<S<int>>);

是trivial的,但是构造函数有点多,就没法默认构造

你问这有什么用,确实没用。当不知道好了

今天也和读者聊天问push back T构造异常了咋办,

那我只能说这个T的实现很没有素质,除了bad alloc别的老子不想管

希望大家都做一个有素质的人

看不懂

随机数生成和场景关联程度太大了,lemire的算法省掉了取余% 但是部分场景性能并不能打败使用取余%的版本

其实就是滚动hash,比如这种

uint32_t hash = 0;
for (size_t i = 0; i < len; i++) {
  hash = hash * B + data[i];
}
return hash;

这个B可能是个质数,比如31,不过不重要

考虑一个字符串子串匹配场景,这种场景下得计算字串hash,比如长字符串内长度为N的子串,代码类似这样

for(size_t i = 0; i < len-N; i++) {
  uint32_t hash = 0;
  for(size_t j = 0; j < N; j++) {
    hash = hash * B + data[i+j];
  }
  //...
}

这个代码的问题是效率低,有没有什么优化办法?

显然这里面有重复计算,到N之前的hash计算完全可以提前算出来

后面变动的减掉就行

uint32_t BtoN = 1;
for(size_t i = 0; i < N; i++) { BtoN *= B; }

uint32_t hash = 0;
for(size_t i = 0; i < N; i++) {
  hash = hash * B + data[i];
}
// ...
for(size_t i = N; i < len; i++) {
  hash = hash * B + data[i] - BtoN * data[i-N];
  // ...
}

不知道你看懂没?

这样提前算好性能翻个五倍没啥问题

代码在这里 https://github.com/lemire/clhash/

还有这个 https://github.com/lemire/rollinghashcpp

视频

不要直接从原类型返回optional这种盒子类型,会破坏RVO 手动make_optional就行了


上一期