Skip to content

Releases: wanghenshui/cppweeklynews

C++ 中文周刊 2025-02-24 第179期

23 Feb 17:04
e6273ca
Compare
Choose a tag to compare

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 点击进入 满了加这个 729240657

RSS

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

本期文章由 YellyHornby,qlql 赞助 在此表示感谢 祝老板身体健康事业顺利


资讯

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

c++26 最新进展!Hagenberg会议之前微信过,这里补一下

本文来自前线记者Mick

Hagenberg (2025-02)总结
这次会议是C++26周期第六次会议,也是C++26 Feature Freeze;到这一步,所有没有进入Stage 3 (wording)的提案都只能推迟到C++29了,因此我们也确定了C++26所可能拥有的特性的最大范围。

这次会议同样是整个周期最重要的会议之一,我们迎来了C++20之后第一个T0级语言特性的通过,迎来了多个历经十年长跑的提案的修成正果,也迎来了C++在安全问题上迈出的第一大步。作为一个不在主场进行的会议,本次会议的参会人数只有200人(比上次少20人左右),但是依然迎来了大量重要的进展。

先来看看这次会议通过进入C++26的提案。语言方面,最重要的提案无疑是P2900 Contracts,这一重量级特性是对C assert()宏的正式承认和发展,加入了新的contract_assert宏和pre/post指示来明确实现函数的前置/后置条件。与assert宏依赖的NDEBUG不同,这些新的Contracts指示将会通过编译选项(-fcontract-semantic=...)指示,并提供了更加细致的四种不同语义(ignore,observe,quick_enforce,enforce)来满足各种场景的需求。Contracts是一个非常大的话题,在这里也没法用几句话囊括它的一切,但作为C++最重要的安全特性之一后面应该会有大量的教程来讲解这个特性

另一个重要的通过的语言特性是P2786 trivially relocatable。平凡迁移指的是那些“移动+析构=memcpy”的类型,而满足这个条件的类型出乎意料地多(例如三家里有两家的vector和string都能平凡迁移)有了语言层面的探测平凡迁移类型的手段之后,vector::resize之类的函数就可以将元素数组整体memcpy过去而不是一个个移动了。遗憾的是,在EWG的长期争吵之后,P2786最终选择了trivially_relocatable_if_eligible和replaceable_if_eligible这两个超级长的关键字名字作为标记一个类型可以平凡迁移的手段,并且为了安全这些关键字只有当所有成员都能平凡迁移的情况下才有效,极大限制了它们的可用性。

除此之外,这次会议还通过了P1976 #embed。这是一个C23的新预处理宏,允许将任意文件作为逗号分隔的列表引入程序。这是对于传统的#include "some_data.txt"的更高级解决方案,速度更快,也没有撞到字符串极限的可能性。它的功能也更强,可以通过#embed "something" limit(5)来限制最大可引入的元素数。

这次会议还通过了P3475(废除没人能懂的memory_order::consume),P2841(允许template<template concept C>和template<template bool B>等构造)

标准库方面,这次会议通过的最重要特性无疑是P3471 Standard Library Hardening,在标准中规定了“强化实现”的概念。在强化实现中,标准库中的各个operator[]和其他常见函数将会使用Contracts强制检查参数没有出界,从而能够有效避免一些最常见的越界UB的发生。和P2900结合,这两个提案是对近期爆发的对C++安全性的批评的有力回应。在P3100/P3599得到实现后,在语言和库方面我们终于对于常见UB有了有效的解决方案。

另一个重要提案是P0447 std::hive。这是一个悲情的提案,初版提出于2016年,经过9年争吵,整整29个revision,以及改名(colony -> hive),以及在2023年险些被直接拒绝,最终还是在这次会议修成正果。这个新的标准顺序容器是一个很有意思的容器,利用特殊的跳表+链状数组结构实现了所有元素的绝对迭代器稳定性(只要不被删除就永远有效)+均摊O(1)的插入/删除/迭代这样看似不可能达成的复杂度。这一特性使得它非常适合在各类游戏项目中储存entity;这些游戏元素很多时候会被不断插入和删除,但是既有的元素的位置不能变。

第三组重要的通过提案是P3372 constexpr容器和P3378 constexpr异常类型。在上次会议允许了编译期抛异常之后,这次将大多数标准异常类型(比如out_of_range,invalid_argument)完成了constexpr化,从而让编译期抛出这些异常成为可能。P3372也让list map deque等容器在编译期的使用成为可能,不过遗憾的是非透明编译期内存分配仍然没有任何解决的希望,从而这些容器的constexpr使用总是让人觉得有些别扭。

除此之外,这次会议还通过了多个SIMD bugfix提案,P3137 views::to_input,P2846 ranges::reserve_hint等Ranges增强;另外P3019 indirect and polymorphic在上次通过后发现了一些问题,这次会议再次通过了一下。

在各特性的进展方面,这次会议是Stage 2 -> 3的deadline,现在还没forward的就都被推迟到29周期去了。

EWG方面,这次会议P2996 静态反射的review已经接近尾声,基本确定将会在下次会议进入Plenary。这次会议通过了几个针对P2996的bugfix和扩展,包括P3547 access_context来限制反射体的获取可能性,以及P3096函数参数的反射。除此之外,BS和HS提出的重量级安全特性Profiles(其实就是写[[profiles::enforce(xxx)]]然后禁掉一些不安全特性,reinterpret_cast之类的)在讨论了一天半之后最终决定不进入C++26,而是改为发布一个白皮书(轻量级TS)来更快推进。(被忽略好几年的TM TS可能也会转换成白皮书)

其他大特性方面,模式匹配最终小比分落败,没能进入C++26,但是在最后一天EWG保住了P1306 Expansion Statement这个重要的反射的补充特性,也算保住了底线吧。接下来CWG将会有比较艰巨的任务,完成反射这个巨大提案的review的同时还被塞了30+篇小提案,最后可能不得不要掉出去一堆了。

LEWG方面,这次会议通过了大量S&R的附属提案,包括P3149 async_scope和P3296 let_async_scope这两个重量级的Structured Concurrency提案(遗憾的是std::task/lazy依然没有太大进展)和一个标准化的系统调度器(P2079)。除此之外,几个著名的提案包括P2988 std::optional<T&>,P3179 Ranges化的多线程算法,P2019线程名称与与栈大小,以及P0260多线程队列也得到了forward的机会,从而有机会进入C++26。接下来LWG的任务较为轻松,只要把那几个S&R的提案搞定其实就差不多了。

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

性能周刊

文章

API design note: Beware of adding an “Other” enum value

考虑这种枚举定义

enum class WidgetFlavor
{
    Vanilla,
    Chocolate,
    Strawberry,
    Other,
};

有个Other,也许有一天,你新添加了一个Flavor,Mint,那么这个Other会不会包含这个新加的Mint?

永远不要用这种歧义的玩意,将来会引入维护性问题

Investigating an argument-dependent lookup issue and working around it

现场长这样

namespace winrt::impl
{
    ⟦ ... ⟧

    template <typename Delegate, typename... Arg>
    bool invoke(Delegate const& delegate, Arg const&... args) noexcept;

    ⟦ ... ⟧

    template <typename Derived, typename AsyncInterface, typename TProgress = void>
    struct promise_base : implements<Derived, AsyncInterface, Windows::Foundation::IAsyncInfo>
    {
        ⟦ ... ⟧

        void set_completed() noexcept
        {
            async_completed_handler_t<AsyncInterface> handler;
            AsyncStatus status;

            ⟦ ... ⟧

            if (handler)
            {
                invoke(handler, *this, status);
            }
        }
    }
}

没有调用内部的invoke,被ADL引入了std::invoke

加上内部命名空间解决

The Weirdest MSVC Address Sanitizer Bug

#include <memory>
#include <type_traits>

template <typename stream>
struct io {
    io(stream) : b(false) {}
    alignas(64) bool b;
};

int main() {
    auto pi = std::make_unique<int>(543);
    using iio = io<int>;
    io x(7);
    static_assert(std::is_same_v<decltype(x), iio>);
}

msvc用 /fsanitize=address会误报。

ODR, libc++ hardening, Profiles and Contracts

简单介绍一下 WG21 和安全profile constract可能对ODR规则造成影响,对于标准库实现存在挑战

Eliminating redundant bound checks

跳转表可能会有边界检查没有优化掉

#include <array>
#include <cstdint>
#include <cstdlib>

static constexpr std::array<size_t, 256> TABLE = {
    798, 553, 541, 345, 276, 698, 861, 448,  898,  588, 678, 593, 265, 611, 915, 835, 893, 3,   411,
    769, 792, 115, 526, 836, 356, 454, 279,  876,  924, 644, 449, 682, 727, 267, 665, 889, 75,  825,
    667, 222, 603, 43,  376, 655, 221, 811,  35,   182, 550, 112, 660, 374, 241, 589, 993, 428, 747,
    673, 11,  954, 296, 181, 842, 91,  764,  805,  155, 916, 431, 380, 860, 970, 998, 982, 445, 144,
    199, 587, 400, 356, 283, 645, 575, 288,  521,  861, 175, 289, 616, 766, 48,  29,  265, 492, 832,
    412, 766, 827, 22,  924, 766, 559, 1012, 826,  391, 254, 871, 347, 615, 529, 789, 606, 259, 4,
    924, 988, 2,   833, 582, 366, 402, 186,  328,  181, 58,  124, 478, 380, 782, 582, 993, 536, 790,
    657, 558, 829, 637, 129, 177, 72,  847,  916,  236, 398, 37,  932, 844, 938, 580, 784, 58,  713,
    490, 12,  680, 525, 940, 30,  241, 345,  1019, 0,   742, 169, 660, 253, 187, 545, 288, 333, 137,
    587, 731, 660, 600, 128, 389, 44,  109,  401,  195, 147, 631, 690, 191, 614, 797, 744, 28,  946,
    348, 851, 889, 279, 521, 724, 897, 92,   773,  635, 212, 339, 978, 639, 282, 414, 691, 365, 706,
    953, 754, 976, 482, 727, 257, 673, 443,  99,   341, 540, 247, 427, 312, 713, 943, 815, 595, 611,
    191, 827, 179, 827, 432, 472, 237, 163,  218,  35,  475, 800, 299, 185, 779, 27,  924, 981, 11,
    504, 160, 9,   342, 938, 69,  745, 575,  791,
};

static size_t first_idx_to_second_idx(uint8_t first_idx)
{
    switch (first_idx) {
    case 0: return 798; case 1: return 553; case 2: return 541; case 3: return 345;
    case 4: return 276; case 5: return 698; case 6: return 861; case 7: return 448;
    case 8: return 898; case 9: return 588; case 10: return 678; case 11: return 593;
    case 12: return 265; case 13: return 611; case 14: return 915; case 15: return 835;
    case 16: return 893; case 17: return 3; case 18: return 411; case 19: return 769;
    case 20: return 792; case 21: return 115; case 22: return 526; case 23: return 836;
    case 24: return 356; case 25: return 454; case 26: return 279; case 27: return 876;
    case 28: return 924; case 29: return 644; case 30: return 449; case 31: return 682;
    case 32: return 727; case 33: return 267; case 34: return 665; case 35: return 889;
    case 36: return 75; case 37: return 825; case 38: return 667; case 39: return 222;
    case 40: return 603; case 41: return 43; case 42: return 376; case 43: return 655;
    case 44: return 221; case 45: return 811; case 46: return 35; case 47: return 182;
    case 48: return 550; case 49: return 112; case 50: return 660; case 51: return 374;
    case 52: return 241; case 53: return 589; case 54: return 993; case 55: return 428;
    case 56: return 747; case 57: return 673; case 58: return 11; case 59: return 954;
    case 60: return 296; case 61: return 181; case 62: return 842; case 63: return 91;
    case 64: return 764; case 65: return 805; case 66: return 155; case 67: return 916;
    case 68: return 431; case 69: return 380; case 70: return 860; case 71: return 970;
    case 72: return 998; case 73: return 982; case 74: return 445; case 75: return 144;
    case 76: return 199; case 77: return 587; case 78: return 400; case 79: return 356;
    case 80: return 283; case 81: return 645; case 82: return 575; case 83: return 288;
    case 84: return 521; case 85: return 861; case 86: return 175; case 87: return 289;
    case 88: return 616; case 89: return 766; case 90: return 48; case 91: return 29;
    case 92: return 265; case 93: return 492; case 94: return 832; case 95: return 412;
    case 96: return 766; case 97: return 827; case 98: return 22; case 99: return 924;
    case 100: return 766; case 101: return 559; case 102: return 1012; case 103: return 826;
    case 104: return 391; case 105: return 254; case 106: return 871; case 107: return 347;
    case 108: return 615; case 109: return 529; case 110: return 789; case 111: return 606;
    case 112: return 259; case 113: return 4; case 114: return 924; case 115: return 988;
    case 116: return 2; case 117: return 833; case 11...
Read more

C++ 中文周刊 2025-02-09 第178期

09 Feb 15:30
27e807a
Compare
Choose a tag to compare

周刊项目地址

qq群 点击进入 满了加这个 729240657

RSS

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

本期文章由 ZIRQ 赞助 在此表示感谢 祝老板发大财身体健康永远不死


资讯

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

编译器信息最新动态推荐关注hellogcc公众号 本周更新 2025-02-05 第292期

性能周刊

文章

shared_ptr overuse in C++

省流 不共享不要滥用

Falsehoods programmers believe about null pointers

列举一些对空指针的误解,比如访问空指针会挂,c/c++语言设定如此,其他语言会捕获异常特殊处理

额我觉得还是不要知道的好

C++26: erroneous behaviour

c++的未定义行为涉及的面太广,有必要收敛一些场景,比如没有初始化读就读这种场景,归纳为EB

如果真的需要这种行为,主动标记[[indeterminiate]] 这种标记下没初始化就使用才被归纳为UB

比如

void foo() {
  int d [[indeterminate]];  // d has an indeterminate value
  bar(d); // that's undefined behaviour!
}

不过目前为止只是提案 P2795,没有编译器支持实现

Thread-safe memory copy

代码鉴赏 安全的memcpy

inline void Relaxed_Memcpy(volatile Atomic8* dst, volatile const Atomic8* src,
                           size_t bytes) {
  constexpr size_t kAtomicWordSize = sizeof(AtomicWord);
  while (bytes > 0 &&
         !IsAligned(reinterpret_cast<uintptr_t>(dst), kAtomicWordSize)) {
    Relaxed_Store(dst++, Relaxed_Load(src++));
    --bytes;
  }
  if (IsAligned(reinterpret_cast<uintptr_t>(src), kAtomicWordSize) &&
      IsAligned(reinterpret_cast<uintptr_t>(dst), kAtomicWordSize)) {
    while (bytes >= kAtomicWordSize) {
      Relaxed_Store(
          reinterpret_cast<volatile AtomicWord*>(dst),
          Relaxed_Load(reinterpret_cast<const volatile AtomicWord*>(src)));
      dst += kAtomicWordSize;
      src += kAtomicWordSize;
      bytes -= kAtomicWordSize;
    }
  }
  while (bytes > 0) {
    Relaxed_Store(dst++, Relaxed_Load(src++));
    --bytes;
  }
}

每个byte都原子store load 线程安全了

对于V8来说,安全比较重要。即使这玩意慢三四十倍

Richard Szabo - Traps with Smart Pointers (Lightning Talk)

省流 shared ptr一律使用make_shared构造

使用alias 构造 搭配weak ptr使用存在问题

如何避免类只能通过make_shared构造?构造函数tag + 静态函数匹配。看嗲吗

class A: std::enable_shared_from_this<A> {
 private:
  struct Private {};
 public:
  A(Private dummy, int member) : member_(member) {}
  template <typename... ArgsT>
  static std::shared_ptr<A> create(ArgsT&&... args) {
    return std::make_shared<A>(Private(), std::forward<ArgsT>(args)...);
  }
 private:
  int member_ = 1;
};

int main() {
  std::cout << "The Start\n\n";
  auto a_sptr = A::create(42);
  std::cout << std::endl << "The End!";
  return 0;
}

Data Storage in Entity Component Systems

介绍数据局部性有利的数据结构 Dense/Sparse Array

简单说就是这个德行

#include <vector>
#include <cassert>

template<typename T>
class DenseSparseArray {
public:
    // 插入元素(假设 entity 是唯一标识)
    void insert(uint32_t entity, const T& value) {
        if (entity >= sparse.size()) {
            sparse.resize(entity + 1, -1); // -1 表示无效索引
        }
        
        if (sparse[entity] == -1) {
            sparse[entity] = dense.size();
            dense.push_back({entity, value});
        }
    }

    // 删除元素
    void erase(uint32_t entity) {
        if (contains(entity)) {
            size_t dense_idx = sparse[entity];
            auto& last = dense.back();

            // 将要删除的元素与最后一个元素交换
            std::swap(dense[dense_idx], last);
            sparse[last.entity] = dense_idx;

            dense.pop_back();
            sparse[entity] = -1;
        }
    }

    // 访问元素
    T& operator[](uint32_t entity) {
        assert(contains(entity));
        return dense[sparse[entity]].value;
    }

    // 判断是否存在
    bool contains(uint32_t entity) const {
        return entity < sparse.size() && sparse[entity] != -1;
    }

    // 迭代器支持
    auto begin() { return dense.begin(); }
    auto end() { return dense.end(); }

private:
    struct Element {
        uint32_t entity;
        T value;
    };

    std::vector<int> sparse;  // 稀疏数组(存储索引)
    std::vector<Element> dense; // 密集数组(实际数据)
};

enseSparseArray<int> arr;

arr.insert(100, 42);  // 插入 entity=100
arr.insert(200, 77);  // 插入 entity=200

std::cout << arr[100]; // 输出 42

arr.erase(100);       // 删除 entity=100

for (auto& elem : arr) { // 遍历所有有效元素
    std::cout << elem.entity << ": " << elem.value << "\n";
}

deepseek帮我写的

这种玩法比较方便遍历且不失访问速度,对于小数据集是非常有用的

另外PPT里还提到了一些优化。感兴趣可以看EnTT代码

Optimizing Mulithreading Performance

简单介绍MESI那套东西,多线程 cache局部性非常重要,另外介绍一些影响性能的场景

  1. 数据竞争,比如atomic fetch add
  2. 数据局部性影响,多个线程访问一份数据
  3. 线程过多,上下文切换开销重
  4. False Sharing,不同数据在同一块cacheline造成互相干扰
    1. 使用std::harware_destructive_interference_size alignas
    2. 典型场景
      1. 线程数组,每个线程访问数组的一个元素,没padding大概率互相干扰
      2. 类似,矩阵计算分块,分的不够开导致互相干扰
      3. 结构体字端访问,没有pading导致互相干扰
      4. 动态分配的小对象,存在可能
        1. 分配大内存专属使用
        2. 尽可能对齐

Class layout

比较常规

介绍了基本的字段大小,继承影响,对齐影响,EBO,no_unique_address, 以及分析工具

clang++ -cc1 -fdump-record-layouts # (or -Xclang -fdump-record-layouts)
g++ -fdump-lang-class (or -fdump-class-hierarchy) # (dumps to a file, so not usable in Compiler explorer)
msvc /d1reportAllClassLayout

simd库横向对比(2023)

vc是c++26simd前身。感觉不是很好用。

业界还是用google highway多一些。

另外社区xmind很火(或者说炒作很火)但是这里没收录,可能是太新了

TypeSanitizer

cat example_AliasViolation.c
int main(int argc, char **argv) {
  int x = 100;
  float *y = (float*)&x;
  *y += 2.0f;          // Strict aliasing violation
  return 0;
}

#Compile and link

clang++ -g -fsanitize=type example_AliasViolation.cc

很好用。想体验的可以试一下

cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang" -DLLVM_ENABLE_RUNTIMES="compiler-rt" <path to source>/llvm

代码鉴赏:成员字段级RAII

来源

godbolt

#include <iostream>
#include <type_traits>
#include <cstddef>

using VoidPtrFn = void (*)(void*);

struct MemberScopeGuard
{
    VoidPtrFn fn;
    ~MemberScopeGuard() { fn(this); }
};

#define TOKEN_PASTE_IMPL(x, y) x##y
#define TOKEN_PASTE(x, y)      TOKEN_PASTE_IMPL(x, y)

#define MEMBER_SCOPE_GUARD(...)                                                                           \
    MemberScopeGuard TOKEN_PASTE(memberScopeGuard, __LINE__)                                              \
    {                                                                                                     \
        ([]<auto Fn>(auto* xThis) -> VoidPtrFn                                                            \
        {                                                                                                 \
            return [](void* x) -> void                                                                    \
            {                                                                                             \
                using T = std::remove_pointer_t<decltype(xThis)>;                                         \
                Fn(*(reinterpret_cast<T*>(                                                                \
                    reinterpret_cast<char*>(x) - offsetof(T, TOKEN_PASTE(memberScopeGuard, __LINE__))))); \
            };                                                                                            \
        }).template operator()<[](auto& self) __VA_ARGS__>(this)                                          \
    }

struct parent {
    int x = 10;
    MEMBER_SCOPE_GUARD({
        std::cout << "Parent pointer is " << &self << '\n';
        std::cout << "Parent x is " << self.x << '\n';
    });

    int y = 25;
    MEMBER_SCOPE_GUARD({
        std::cout << "Parent pointer is " << &self << '\n';
        std::cout << "Parent y is " << self.y << '\n';
    });
};

int main() {
    parent x;
    std::cout << "Address of parent is " << &x << '\n';
}

除了感叹c++真夸张之外没什么用。

另外这个字段guard必须得放在成员后面,不能使用guard后面的字段(反向析构顺序:)

感谢天失败投稿以及原作者 Vittorio Romeo

开源项目介绍

  • asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群753302367和作者对线

互动环节

街霸6不知火舞联动上线了,非常好玩


上一期

本期

下一期

C++ 中文周刊 2025-02-01 第177期

01 Feb 14:22
a9e2798
Compare
Choose a tag to compare

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 点击进入 满了加这个 729240657

RSS

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

本期文章由 F.v.S 祥子 Jared 赞助 在此表示感谢

祝大家新年快乐。又要返工了


资讯

标准委员会动态/ide/编译器信息放在这里 c++26最新进展可以看这个帖子

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

性能周刊

文章

C++26: attributes in structured bindings

结构化绑定的参数可以标记属性attr,比如

std::map<int, std::string> m{
        {1, "one"}, {2, "two"} {3, "three"}};

for(const auto& [ [[maybe_unused]] k, v]: m) {
        DEBUG(k) // only used in debug builds
    std::cout << v << '\n';
}

A pattern for obtaining a single value while holding a lock

通过返回值复制,而不是手动赋值。规避潜在的多余构造

std::mutex m_mutex;
Widget m_widget;

// Get a copy of the widget
Widget widget;
{
    auto guard = std::lock_guard(m_mutex);
    widget = m_widget;
}

这是常规写法,不好。倾向于通过返回值一次赋值

// Get a copy of the widget
Widget widget = [&] {
    auto guard = std::lock_guard(m_mutex);
    return m_widget;
}();

这种lambda用的多了你也可以通过封装函数来返回

Widget CopySavedWidget()
{
    auto guard = std::lock_guard(m_mutex);
    return m_widget;
}

类似的保存-销毁需求

Widget widget = [&] {
    auto guard = std::lock_guard(m_mutex);
    return std::exchange(m_widget, {});
}();

也可以这样封装,更泛化一点

template<typename T>
Widget ExchangeSavedWidget(T&& value)
{
    auto guard = std::lock_guard(m_mutex);
    return std::exchange(m_widget, std::forward<T>(value)):
}

PPSSPP or psp? Uncovering bugs from the past

鉴赏几个PPSSPP项目的bug

static int sceNetAdhocctlGetAddrByName(const char *nickName,
                                       u32 sizeAddr, u32 bufAddr)
{
  ....
  // Copied to null-terminated var to prevent unexpected behaviour on Logs
  memcpy(nckName, nickName, ADHOCCTL_NICKNAME_LEN); 

  ....
  if (netAdhocctlInited)
  {
    // Valid Arguments
    if (nickName != NULL && buflen != NULL)
    {
      ....
    }
    ....
  }
}

如果if里的nickName存在等于nullptr的可能,那么memcpy的行为会有UB,那么这个代码必然有问题了

int internal_profiler_find_cat(const char *category_name, bool create_missing)
{
  int i;
  for (i = 0; i < MAX_CATEGORIES; i++)
  {
    const char *catname = categories[i].name;
    if (!catname)
      break;
#ifdef UNIFIED_CONST_STR
    if (catname == category_name)
    {
#else
    if (!strcmp(catname, category_name))                          // <=
    {
#endif
      return i;
    }
  }

  if (i < MAX_CATEGORIES && category_name && create_missing)      // <=
  {
    ....
  }     
}

没有检查category_name是不是nullptr就放到strcmp里,潜在bug

static void __GameModeNotify(u64 userdata, int cyclesLate)
{
  ....
  if (gameModeSocket < 0)
  {
    // ReSchedule
    CoreTiming::ScheduleEvent(usToCycles(GAMEMODE_UPDATE_INTERVAL) - cyclesLate,
                              gameModeNotifyEvent, userdata);
    return;
  }

  auto sock = adhocSockets[gameModeSocket - 1];
  ....
}

数组索引可能是-1

SoftGPU::SoftGPU(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
  : GPUCommon(gfxCtx, draw)
{
  ....
  drawEngine_ = new SoftwareDrawEngine();
  if (!drawEngine_)
    return;
  ....
}

这个指针检查屁用没有,new不出来会抛异常。如果在意就用catch兜住啊

static std::vector<MicWaitInfo> waitingThreads;
....
static void __MicBlockingResume(u64 userdata, int cyclesLate)
{
  ....
  int count = 0;
  for (auto waitingThread : waitingThreads)
  {
    if (waitingThread.threadID == threadID)
    {
      ....
      if (Microphone::isHaveDevice())
      {
        if (Microphone::getReadMicDataLength() >= waitingThread.needSize)
        {
          ....
          waitingThreads.erase(waitingThreads.begin() + count);    // <=
        }
        else
        {
          ....
        }
      } 
      else
      {
        ....
        waitingThreads.erase(waitingThreads.begin() + count);      // <=
        readMicDataLength += waitingThread.needSize;
      }
    }

    ++count;
  }
}

经典问题,循环中删除

void Int_VecDo3(MIPSOpcode op)
{
  ....
  u32 lastsat = (currentMIPS->vfpuCtrl[VFPU_CTRL_DPREFIX] & 3) << (n + n - 2);
  ....
}

n + n -2可能是负数 <<就UB了

inline float Float16ToFloat(float16 ix)
{
    float x;
    memcpy(&x, &ix, sizeof(float));
    return x;
}

复制越界

void Jit::Comp_SVQ(MIPSOpcode op)
{
  CONDITIONAL_DISABLE(LSU_VFPU);

  int imm = (signed short)(op&0xFFFC);
  int vt = (((op >> 16) & 0x1f)) | ((op&1) << 5);
  MIPSGPReg rs = _RS;

  CheckMemoryBreakpoint(0, rs, imm);

  switch (op >> 26)
  {
    case 53: //lvl.q/lvr.q
    {
      if (!g_Config.bFastMemory)
      {
        DISABLE;
      }

      DISABLE;
  ....
}

代码语义重复 DISABLE无论如何都能走到

void BlockAllocator::Block::DoState(PointerWrap &p)
{
  char tag[32];  
....
  size_t tagLen = strlen(tag);
  if (tagLen != sizeof(tag))
    memset(tag + tagLen, 0, sizeof(tag) - tagLen);
  DoArray(p, tag, sizeof(tag));
}

多余的if分支,永远true

void netAdhocValidateLoopMemory()
{
  // Allocate Memory if it wasn't valid/allocated
  // after loaded from old SaveState
  if (   !dummyThreadHackAddr
      || (   dummyThreadHackAddr
          && strcmp("dummythreadhack",
                    kernelMemory.GetBlockTag(dummyThreadHackAddr)) != 0))
  {
  ....
}

条件重复

void QueueCallback(void (*func)(VulkanContext *vulkan, void *userdata),
                   void *userdata)
{ 
  callbacks_.push_back(Callback(func, userdata));
}
void VulkanRenderManager::EndCurRenderStep()
{
  for (VKRGraphicsPipeline *pipeline : pipelinesToCheck_)
  {
    if (!pipeline)
    {
      // Not good, but let's try not to crash.
      continue;
    }

    if (!pipeline->pipeline[(size_t)rpType])
    {
      pipeline->pipeline[(size_t)rpType] = Promise<VkPipeline>::CreateEmpty();
      _assert_(renderPass);
      compileQueue_.push_back(CompileQueueEntry(pipeline,
            renderPass->Get(vulkan_, rpType, sampleCount),rpType, sampleCount));
      needsCompile = true;
    }
  }
}

push_back不如emplace_back

C stdlib isn’t threadsafe and even safe Rust didn’t save us

省流 setenv/getenv不是线程安全的。涉及到多个语言runtime读写env了。倒霉赶上了属于是

How to Simplify Object Comparisons with Ties in C++11/14

省流,tie

struct Person
{
    std::string first_name;
    std::string last_name;
    std::uint8_t age;

    auto tied() const
    {
        return std::tie(first_name, last_name, age);
    }

    bool operator==(const Person& other) const
    {
        return tied() == other.tied();
    }

    bool operator<(const Person& other) const
    {
        return tied() < other.tied();
    }
};

开源项目介绍


上一期 下一期

C++ 中文周刊 2025-01-25 第176期

25 Jan 17:04
Compare
Choose a tag to compare

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 点击进入 满了加这个 729240657

RSS

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


资讯

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

安全问题还在吵架

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

性能周刊

文章

Measuring code size and performance

他的场景 不用异常处理错误,还是快的。虽然这些年来异常已经进化了快了一些

Reminder: When a C++ object fails to construct, the destructor does not run

注意你手写的guard类 构造函数不能失败,否则构造失败不析构就泄漏了

Regular expressions can blow up!

省流,fuck std::regex

#include <iostream>
#include <regex>

int main() {
    std::string text = "Everyone loves Lucy.";
    std::regex pattern(R"(.*+s}}@w)"); 
    // Perform regex search
    std::smatch match;
    bool found = std::regex_search(text, match, pattern);
    std::cout << "Regex search result: " 
          << (found ? "Match found" : "No match") << std::endl;
    return 0;
}

这段代码运行七分钟

std::nontype_t: What is it, and Why

抽象type 一个tag 重载 帮助function ref匹配constexpr函数

Protecting Coders From Ourselves: Better Mutex Protection

省流,boost::synchronized_value

folly也有synchronizedwith<T>

感觉这个周刊写的越多越发现重复。。评论区不知道的说一下,不行就folly组件挨个介绍

借助 Windsurf Sonnet Debug 过程一例

虽然和c++没啥关系,分享只是感叹AI太强了,程序员真的有点可有可无了

Parsing JSON in C & C++: Singleton Tax

省流 池化加速助力 解析加快 局部性功劳

Pipeline architectures in C++ - Boguslaw Cyganek - Meeting C++ 2024

他讲的不是CPU那个pipeline,也不是任务调度那个pipeline,讲的是这个比玩意

template < typename InT, typename InE, typename Function >
requires std::invocable< Function, std::expected< InT, InE > >
			&& is_expected< typename std::invoke_result_t< Function, std::expected< InT, InE > > >
constexpr auto operator | ( std::expected< InT, InE > && ex, Function && f ) -> typename std::invoke_result_t< Function, std::expected< InT, InE > >
{
	return std::invoke( std::forward< Function >( f ), /***/ std::forward< std::expected< InT, InE > >( ex ) );
}

....

	// ----------------------------------------------------------------------------------------------------------------
	// Here we create our CUSTOM PIPE
	auto res = 	PayloadOrError { Payload { "Start string ", 42 } } | Payload_Proc_1 | Payload_Proc_2 | Payload_Proc_3 ;
	// ----------------------------------------------------------------------------------------------------------------
	 

就是类似range的pipe语法传播

如何评价,光顾着耍帅了有点

C++ programmer's guide to undefined behavior: part 12 of 11

继续介绍坑点

std::reserve和std::resize区别

resize会初始化0, size == capacity

reserve不会 size == 0

但没有resize_with_overwrite这种跳过填0的操作,只有string有

resize reserve非常容易用错

注意无符号数取反问题

struct Element {
  size_t width; // original non-scaled width
  ....
};

// You are using smart component system that uses
// IDs to refer to elements.
using ElementID = uint64_t; 

// Positions in OpenGL/DirectX/Vulkan worlds are floats
struct Offset {
  float x;
  float y;
};

size_t get_width(ElementID);
float screen_scale();
void move_by(ElementID, Offset);

void on_unchecked(ElementID el) {
  auto w = get_width(el);
  move_by(el, Offset {
    -w * screen_scale() * 0.3f,
    0.0f
  });
}

这个-w必有问题,另外编译选项查不出来

对齐和隐式创建引发的问题

#pragma pack(1)
struct Record {
  long value;
  int data;
  char status;
};

int main() {
  Record r { 42, 42, 42};
  static_assert(sizeof(r) == sizeof(int) + sizeof(char) + sizeof(long));
  std::cout <<
    std::format("{} {} {}", r.data, r.status, r.value); // 42 - '*'
}

看这没问题?

int main() {
  Record records[] = { { 42, 42, 42}, { 42, 42, 42}  };
  static_assert(sizeof(records) ==
                2 * ( sizeof(int) + sizeof(char) + sizeof(long) ));
  for (const auto& r: records) {
    std::cout << std::format("{} {} {}", r.data, r.status, r.value); // 42 - '*'
  }
}

改成两个就会炸

问题来自赋值隐式创建int 但实际上不是int,是pack bit

怎么解决?复制一份

int main() {
  Record records[] = { { 42, 42, 42}, { 42, 42, 42}  };
  for (const auto& r: records) {

    // C++23 has wonderful auto() for this purpose
    std::cout << std::format("{} {} {}",
      auto(r.data), auto(r.status), auto(r.value)); 

    // In C++20,
    auto data = r.data; auto status = r.status; auto value = r.value;
    std::cout << std::format("{} {} {}", data, status, value); 

    // Or completely ugly and unstable to type changes
    std::cout << std::format("{} {} {}", static_cast<int>(r.data), 
                                         static_cast<char>(r.status), 
                                         static_cast<long>(r.value>));
  }
}

但实际上编译器应该阻止这种行为

后面还有coroutine的问题,特长。我准备拆开单独写一下

一个cmakelist warn设置

set (MY_CXX_FLAGS
"-Wall \
-Wextra \
-Werror \
-Wsuggest-override \
-Wno-unknown-warning-option \
-Wno-array-bounds \
-pedantic-errors" )
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MY_CXX_FLAGS}")

开源项目介绍


上一期 下一期

C++ 中文周刊 2025-01-12 第175期

12 Jan 16:40
78aca9d
Compare
Choose a tag to compare

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 点击进入 满了加这个 729240657

RSS

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

本期文章由 MSK 赞助 老板大气祝老板永远不死


资讯

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

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

文章

Counting the digits of 64-bit integers

其实比较常规,就是查表

int int_log2(uint64_t x) { return 63 - __builtin_clzll(x | 1); } // c++20可以用bit_width
int digit_count(uint64_t x) {
  static uint64_t table[] = {9,
                             99,
                             999,
                             9999,
                             99999,
                             999999,
                             9999999,
                             99999999,
                             999999999,
                             9999999999,
                             99999999999,
                             999999999999,
                             9999999999999,
                             99999999999999,
                             999999999999999ULL,
                             9999999999999999ULL,
                             99999999999999999ULL,
                             999999999999999999ULL,
                             9999999999999999999ULL};
  int y = (19 * int_log2(x) >> 6);
  y += x > table[y];
  return y + 1;
}

或者更极端一点

int alternative_digit_count(uint64_t x) {
static uint64_t table[64][2] = {
    { 0x01, 0xfffffffffffffff6ULL },
    { 0x01, 0xfffffffffffffff6ULL },
    { 0x01, 0xfffffffffffffff6ULL },
    { 0x01, 0xfffffffffffffff6ULL },
    { 0x02, 0xffffffffffffff9cULL },
    { 0x02, 0xffffffffffffff9cULL },
    { 0x02, 0xffffffffffffff9cULL },
    { 0x03, 0xfffffffffffffc18ULL },
    { 0x03, 0xfffffffffffffc18ULL },
    { 0x03, 0xfffffffffffffc18ULL },
    { 0x04, 0xffffffffffffd8f0ULL },
    { 0x04, 0xffffffffffffd8f0ULL },
    { 0x04, 0xffffffffffffd8f0ULL },
    { 0x04, 0xffffffffffffd8f0ULL },
    { 0x05, 0xfffffffffffe7960ULL },
    { 0x05, 0xfffffffffffe7960ULL },
    { 0x05, 0xfffffffffffe7960ULL },
    { 0x06, 0xfffffffffff0bdc0ULL },
    { 0x06, 0xfffffffffff0bdc0ULL },
    { 0x06, 0xfffffffffff0bdc0ULL },
    { 0x07, 0xffffffffff676980ULL },
    { 0x07, 0xffffffffff676980ULL },
    { 0x07, 0xffffffffff676980ULL },
    { 0x07, 0xffffffffff676980ULL },
    { 0x08, 0xfffffffffa0a1f00ULL },
    { 0x08, 0xfffffffffa0a1f00ULL },
    { 0x08, 0xfffffffffa0a1f00ULL },
    { 0x09, 0xffffffffc4653600ULL },
    { 0x09, 0xffffffffc4653600ULL },
    { 0x09, 0xffffffffc4653600ULL },
    { 0x0a, 0xfffffffdabf41c00ULL },
    { 0x0a, 0xfffffffdabf41c00ULL },
    { 0x0a, 0xfffffffdabf41c00ULL },
    { 0x0a, 0xfffffffdabf41c00ULL },
    { 0x0b, 0xffffffe8b7891800ULL },
    { 0x0b, 0xffffffe8b7891800ULL },
    { 0x0b, 0xffffffe8b7891800ULL },
    { 0x0c, 0xffffff172b5af000ULL },
    { 0x0c, 0xffffff172b5af000ULL },
    { 0x0c, 0xffffff172b5af000ULL },
    { 0x0d, 0xfffff6e7b18d6000ULL },
    { 0x0d, 0xfffff6e7b18d6000ULL },
    { 0x0d, 0xfffff6e7b18d6000ULL },
    { 0x0d, 0xfffff6e7b18d6000ULL },
    { 0x0e, 0xffffa50cef85c000ULL },
    { 0x0e, 0xffffa50cef85c000ULL },
    { 0x0e, 0xffffa50cef85c000ULL },
    { 0x0f, 0xfffc72815b398000ULL },
    { 0x0f, 0xfffc72815b398000ULL },
    { 0x0f, 0xfffc72815b398000ULL },
    { 0x10, 0xffdc790d903f0000ULL },
    { 0x10, 0xffdc790d903f0000ULL },
    { 0x10, 0xffdc790d903f0000ULL },
    { 0x10, 0xffdc790d903f0000ULL },
    { 0x11, 0xfe9cba87a2760000ULL },
    { 0x11, 0xfe9cba87a2760000ULL },
    { 0x11, 0xfe9cba87a2760000ULL },
    { 0x12, 0xf21f494c589c0000ULL },
    { 0x12, 0xf21f494c589c0000ULL },
    { 0x12, 0xf21f494c589c0000ULL },
    { 0x13, 0x7538dcfb76180000ULL },
    { 0x13, 0x7538dcfb76180000ULL },
    { 0x13, 0x7538dcfb76180000ULL },
    { 0x13, 0x7538dcfb76180000ULL },
};
  int log = int_log2(x);
  uint64_t low = table[log][1];
  uint64_t high = table[log][0];
  return (x + low < x ) + high;
}

评论区大哥给了一个更快的

inline uint8_t digitCounts64[]{ 19, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10,
	10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 1, 1 };

inline uint64_t digitCountThresholds64[]{ 0ull, 9ull, 99ull, 999ull, 9999ull, 99999ull, 999999ull, 9999999ull, 99999999ull, 999999999ull, 9999999999ull,
	99999999999ull, 999999999999ull, 9999999999999ull, 99999999999999ull, 999999999999999ull, 9999999999999999ull, 99999999999999999ull, 999999999999999999ull,
	9999999999999999999ull };

inline uint64_t fastDigitCount(const uint64_t inputValue) {
	const uint64_t originalDigitCount{ digitCounts64[__builtin_clzll(inputValue)] };
	return originalDigitCount + (inputValue > digitCountThresholds64[originalDigitCount]);
}

另外这个大哥写了个json库很快。和glaze有一拼 https://github.com/RealTimeChris/Jsonifier/

C++26: a placeholder with no name

总算不用使用 decltype(std::ignore) _;了 c++26支持了

Inside STL: Waiting for a std::atomic<std::shared_ptr> to change, part 1 Part2

省流std::atomic<std::shared_ptr>::notify_one linux平台由于futex的问题,没有很好的实现

windows有waitonaddress

constexpr std::string的一个好处

可以写编译期测试 static_assert

constexpr std::string
decode(std::span<const unsigned char> payload)
{
  static constexpr unsigned char MASK{0x7F};
  std::string                    result{};

  int           shift{};
  unsigned char mask{MASK};
  for(unsigned char scratch{}; auto c : payload) {
    const unsigned char realChar =
      ((c & mask) << shift) | scratch;
    scratch = (c & (~mask)) >> (7 - shift);

    ++shift;
    mask >>= 1;

    result += static_cast<char>(realChar);

    if(7 == shift) {
      result += static_cast<char>(scratch);
      shift   = 0;
      mask    = MASK;
      scratch = 0;
    }
  }

  return result;
}

constexpr auto make_array(auto... values)
{
  return std::array<unsigned char, sizeof...(values)>{
    static_cast<unsigned char>(values)...};
}

void Use()
{
  constexpr auto payload{make_array(0xd3,
                                    0x74,
                                    0x1b,
                                    0xce,
                                    0x2e,
                                    0x83,
                                    0xa6,
                                    0xcd,
                                    0x29,
                                    0x88,
                                    0x5e,
                                    0xc6,
                                    0xd3,
                                    0x5d)};
  constexpr auto payload2{make_array(0xc8,
                                     0x32,
                                     0x9b,
                                     0xfd,
                                     0x66,
                                     0x81,
                                     0x86,
                                     0xab,
                                     0x55,
                                     0x08,
                                     0x44,
                                     0x45,
                                     0xa7,
                                     0xe7,
                                     0xa0,
                                     0xf4,
                                     0x1c,
                                     0x34,
                                     0x46,
                                     0x97,
                                     0xc7,
                                     0xeb,
                                     0xb4,
                                     0xfb,
                                     0x0c,
                                     0x0a,
                                     0x83,
                                     0xe6,
                                     0x74,
                                     0xb2,
                                     0x4e,
                                     0x37,
                                     0xa7,
                                     0xcb,
                                     0xd3,
                                     0xee,
                                     0x33,
                                     0x28,
                                     0x4c,
                                     0x07,
                                     0x8d,
                                     0xdf,
                                     0x6d,
                                     0x78,
                                     0x9a,
                                     0x5d,
                                     0x06,
                                     0xd1,
                                     0xd3,
                                     0xed,
                                     0x72,
                                     0x08,
                                     0xe4,
                                     0x4c,
                                     0x8f,
                                     0xcb,
                                     0x2c,
                                     0x50,
                                     0x7a,
                           ...
Read more

C++ 中文周刊 2024-12-29 第174期

12 Jan 16:39
c0e91ee
Compare
Choose a tag to compare

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 点击进入

RSS

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

本期文章由 Amnesia wyhqaq HNY 赞助 在此表示感谢

老板大气祝老板永远不死


资讯

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

十二月邮件列表

[编译器信息最新动态推荐关注hellogcc公众号 本周更新 2024-01-04 第286期](OSDT Weekly 2024-12-25 第286期 )

文章

Retrofitting spatial safety to hundreds of millions of lines of C++

google在 c++安全上的实践,主要是采用libc++ harden mode

非常推荐使用。我之前搞了个安全的介绍,感兴趣可以看一下

Structured Binding Upgrades in C++26

支持字段级别设置attr了 p0609

auto [it, inserted [[maybe_unused]] ] = map.try_emplace(key, value);

支持在条件中展开,类似c++17那个条件中赋值

if (auto [n] = f()) { ... }

while (auto [header, body] = receive_packet()) { ... }

展开绑定到tuple p1061

template <class T>
auto tie_as_tuple(T& x) {
    auto& [...xs] = x;
    return std::tie(xs...);
}

如果这个能支持,真是有点改变相关工具生态了 magic get/boost pfr可是做了好多脏活

How to Hash Objects Without Repetition: std::hash can be DRY

介绍hash combine和 tie组合的。

#include <string>
#include <tuple>
#include <cassert>
#include <cstdint>
#include <boost/functional/hash.hpp>
#include <iostream>

namespace some_lib {
    template <typename T>
    constexpr void hash_combine(size_t& seed, const T& value)
    {
        seed ^= std::hash<T>{}(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
}

template <typename... TValues>
constexpr auto combined_hash(const TValues&... values)
{
    size_t seed{};
    (..., some_lib::hash_combine(seed, values));

    return seed;
}

struct HashableForTiedMembers
{ };

template <typename T>
constexpr bool hashing_for_tied_members = false; 

template <typename T>
concept HashableForTied = std::derived_from<T, HashableForTiedMembers> || hashing_for_tied_members<T>;

template <HashableForTied T>
struct std::hash<T>
{
    size_t operator()(const T& value) const
    {
        auto hasher = [](const auto&... args) {
            return combined_hash(args...);
        };
        return std::apply(hasher, value.tied());
    }
};

template <typename T>
concept Hashable = requires {
    { std::hash<T>{}(std::declval<T>()) } -> std::convertible_to<size_t>;
};

/////////////////////////////////////////////////////////////////////////
// Person class - opt-in through inheritance

struct Person : HashableForTiedMembers
{
    std::string first_name;
    std::string last_name;
    std::uint8_t age;

    Person(std::string first_name, std::string last_name, std::uint8_t age)
        : first_name{std::move(first_name)}
        , last_name{std::move(last_name)}
        , age{age}
    {
    }

    auto tied() const
    {
        return std::tie(first_name, last_name, age);
    }

    bool operator==(const Person& other) const
    {
        return tied() == other.tied();
    }

    auto operator<=>(const Person& other) const
    {
        return tied() == other.tied();
    }
};

static_assert(Hashable<Person>);

//////////////////////////////////////////////////////////////////////////
// AggregatePerson - opt-in through variable template specialization
struct AggregatePerson
{
    std::string first_name;
    std::string last_name;
    std::uint8_t age;

    auto tied() const
    {
        return std::tie(first_name, last_name, age);
    }
};

template <>
constexpr bool hashing_for_tied_members<AggregatePerson> = true;

int main()
{
    Person p1{"John", "Doe", 33};
    Person p2{"John", "Doe", 33};
    Person p3{"John", "Don", 44};

    assert(p1 == p2);
    assert(p2 < p3);

    assert(std::hash<Person>{}(p1) == std::hash<Person>{}(p2));
    assert(std::hash<Person>{}(p1) == std::hash<Person>{}(p3));

    AggregatePerson ap1{"John", "Doe", 30};
	AggregatePerson ap2{"John", "Doe", 30};
	AggregatePerson ap3{"John", "Dog", 30};

	assert(std::hash<AggregatePerson>{}(ap1) == std::hash<AggregatePerson>{}(ap2));
	assert(std::hash<AggregatePerson>{}(ap1) != std::hash<AggregatePerson>{}(ap3));

    return std::hash<Person>{}(p1);
}

其实他这个设计就是hash_append

    template <class HashAlgorithm>
    friend void hash_append(HashAlgorithm& h, X const& x) noexcept
    {
        //
    }

他这个写法就是把hash算法和x拆出来,x本身转pack 方便归一计算,用的tie 避免开销

combined_hash其实就是hash_append 只不过算法h不能定制

不知道读者们了不了解n3980 hash_append 不了解没关系。就是函数接口定制和这里的代码一个意思

Measuring std::unordered_map Badness

非常搞笑的场景,同样的hashmap float 做key和 int做key冲突率不同 因为值域不同。float表达式导致的

针对float做key要注意hash算法

Inside STL: The atomic shared_ptr

和shared_ptr差不多,为了原子 使用tag pointer 用特殊字段做spinlock 代码在这里

	  while (!_M_val.compare_exchange_strong(__current,
						 __current | _S_lock_bit,
						 __o,
						 memory_order_relaxed))
	    {
	      _GLIBCXX_TSAN_MUTEX_TRY_LOCK_FAILED(&_M_val);
#if __glibcxx_atomic_wait
	      __detail::__thread_relax();
#endif
	      __current = __current & ~_S_lock_bit;
	      _GLIBCXX_TSAN_MUTEX_TRY_LOCK(&_M_val);
	    }
	  _GLIBCXX_TSAN_MUTEX_LOCKED(&_M_val);
	  return reinterpret_cast<pointer>(__current);
	}

Type-safe Enum Class Bit Flags

直接贴代码

#include <bitset>
#include <ostream>
#include <type_traits>
#include <utility>

// Helper class for bitwise flag-like operations on scoped enums.
//
// This class provides a way to represent combinations of enum values without
// directly overloading operators on the enum type itself. This approach
// avoids ambiguity in the type system and allows the enum type to continue
// representing a single value, while the BitFlags can hold a combination
// of enum values.
//
// Example usage:
//
// enum class MyEnum { FlagA = 1 << 0, FlagB = 1 << 1, FlagC = 1 << 2 };
//
// BitFlags<MyEnum> flags = { MyEnum::FlagA, MyEnum::FlagC };
// flags.Unset(MyEnum::FlagA);
// if (flags.IsSet(MyEnum::FlagC)) {
//   // ...
// }
//
// flags |= MyEnum::FlagB;
// BitFlags<MyEnum> new_flags = ~flags;
template <typename T>
class BitFlags {
    using UnderlyingT = std::underlying_type_t<T>;

   public:
    constexpr BitFlags() : flags_(static_cast<UnderlyingT>(0)) {}
    constexpr explicit BitFlags(T v) : flags_(ToUnderlying(v)) {}
    constexpr BitFlags(std::initializer_list<T> vs) : BitFlags() {
        for (T v : vs) {
            flags_ |= ToUnderlying(v);
        }
    }

    // Checks if a specific flag is set.
    constexpr bool IsSet(T v) const {
        return (flags_ & ToUnderlying(v)) == ToUnderlying(v);
    }
    // Sets a single flag value.
    constexpr void Set(T v) { flags_ |= ToUnderlying(v); }
    // Unsets a single flag value.
    constexpr void Unset(T v) { flags_ &= ~ToUnderlying(v); }
    // Clears all flag values.
    constexpr void Clear() { flags_ = static_cast<UnderlyingT>(0); }

    constexpr operator bool() const {
        return flags_ != static_cast<UnderlyingT>(0);
    }

    friend constexpr BitFlags operator|(BitFlags lhs, T rhs) {
        return BitFlags(lhs.flags_ | ToUnderlying(rhs));
    }
    friend constexpr BitFlags operator|(BitFlags lhs, BitFlags rhs) {
        return BitFlags(lhs.flags_ | rhs.flags_);
    }
    friend constexpr BitFlags operator&(BitFlags lhs, T rhs) {
        return BitFlags(lhs.flags_ & ToUnderlying(rhs));
    }
    friend constexpr BitFlags operator&(BitFlags lhs, BitFlags rhs) {
        return BitFlags(lhs.flags_ & rhs.flags_);
    }
    friend constexpr BitFlags operator^(BitFlags lhs, T rhs) {
        return BitFlags(lhs.flags_ ^ ToUnderlying(rhs));
    }
    friend constexpr BitFlags operator^(BitFlags lhs, BitFlags rhs) {
        return BitFlags(lhs.flags_ ^ rhs.flags_);
    }

    friend constexpr BitFlags& operator|=(BitFlags& lhs, T rhs) {
        lhs.flags_ |= ToUnderlying(rhs);
        return lhs;
    }
    friend constexpr BitFlags& operator|=(BitFlags& lhs, BitFlags rhs) {
        lhs.flags_ |= rhs.flags_;
        return lhs;
    }
    friend constexpr BitFlags& operator&=(BitFlags& lhs, T rhs) {
        lhs.flags_ &= ToUnderlying(rhs);
        return lhs;
    }
    friend constexpr BitFlags& operator&=(BitFlags& lhs, BitFlags rhs) {
        lhs.flags_ &= rhs.flags_;
        return lhs;
    }
    friend constexpr BitFlags& operator^=(BitFlags& lhs, T rhs) {
        lhs.flags_ ^= ToUnderlying(rhs);
        return lhs;
    }
    friend constexpr BitFlags& operator^=(BitFlags& lhs, BitFlags rhs) {
        lhs.flags_ ^= rhs.flags_;
        return lhs;
    }

    friend constexpr BitFlags operator~(const BitFlags& bf) {
        return BitFlags(~bf.flags_);
    }

    friend constexpr bool operator==(const BitFlags& lhs, const BitFlags& rhs) {
        return lhs.flags_ == rhs.flags_;
    }
    friend constexpr bool operator!=(const BitFlags...
Read more

C++ 中文周刊 2024-11-23 第173期

23 Nov 14:48
1708e52
Compare
Choose a tag to compare

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 点击进入 满了加这俩 729240657 866408334

RSS

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

本期文章由 LH_mouse 赞助 在此表示感谢

祝老板身体健康事业顺利开新车装新房夜夜当新郎全世界都有你的丈母娘


资讯

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

来自本台记者Mick前方发来的报道

欢迎来到C++26的第五次会议,也是feature freeze之前的倒数第二次会议。本次会议共约230人参与,31个NB参会,依然是传统的线下:线上=2:1模式

在通过的提案方面,本次共通过8篇语言提案和19篇库提案。这比上次会议的7+12要多一些,和东京正好持平。

语言方面,最重磅的也是最坎坷的提案无疑是P1061 auto [...xs] = ... (structured binding packs),作为上次Plenary中被撤销的提案,P1061本来应该波澜不惊地再次投票。没想到的是,EWG第三天的讨论中MSVC开发者提出了对实现难度的抗议,差一点导致本提案倒在Stage 2。幸运的是,最后作者找到了一个妥协方案,将带pack的structured binding限制在只能在模版中使用,从而成功进入标准。除此之外,语言方面还通过了P3068,允许在编译期抛出异常。(当然,异常不能离开编译期,编译抛运行catch是不行的),并且常规deprecate了一批特性(is_trivial,不带逗号的varargs语法)。

LWG方面,最重磅的提案无疑是极度坎坷的P1928 std::simd。本提案的历史极其悠久,从2013年的N3759初创之后,-> N4184/5 -> N4395 -> P0214R9这14个revision之后终于在2018年修成半个正果,成功进入Parallelism TS v2。不过,随后的IS merge依然极度艰难,大规模的设计改动和名称反复贯穿了P1928的历史,最终在用了十年,30个revision之后终于成功进入C++26。本提案事实上大体标准化了SIMD指令,让标准中可以直接像操纵其他原生类型一样操作SIMD向量。至此,C++26标准库的两个T0和一个T0.5特性均已成功进入标准,L(E)WG成功提前完成了自己本周期的目标。下一次会议的主要目标看来就是搞定hive这个老大难。
另一个重点提案就是P3019 indirect/polymorphic,即一个deep copy版的智能指针。从cloned_ptr走到indirect_value走到indirect,花了P0201R6 -> P3019R11的19个revision才走完这条路,不过好歹是走完了。现在pimpl就可以用标准解法了。

除此之外,本次搞定了大量的线性代数bugfix提案,包括aligned_accessor,submdspan fix等对C++26至关重要的提案被成功完成(还剩下atomic_accessor和rank-2k两个提案,预计下次吧)。另外,本次会议还迈出了C23 rebase的第一步,成功将C23新增的两个头文件(bits,安全整数加法)加入了C++26。

本次会议的Stage 2工作组相对来说更有看点一些。EWG方面,P2786平凡迁移和P2900 Contracts均成功被推进Stage 3,但是两者的争议都非常大,forwarding poll可以说是barely consensus,要避免Plenary的失败还有很长的路要走。第三天的讨论中,反射终于确定了使用^^语法(unibrow),并逐渐开始在一些post-P2996反射提案上有了进展(比如consteval block,可惜expansion statement依然卡死着)。第四天早上是模式匹配的主场,虽然这一特性进入26的希望已经非常渺茫,但是P2688仍然在为此努力,并成为了EWG选择的语法而不是P2392的is/as。较为遗憾的是,原本应该在本次会议通过的fiber_context在最后一刻找到了Windows下的实现难题,只得推迟到下个周期去了。

LEWG方面,P2996反射和P2900 Contracts同样进入了Stage 3,从而扫清了这两个语言的主要目标在库这边的障碍。除此之外,整周大部分都在搞S&R相关扩展,例如async_scope,system scheduler等对P2300发挥作用至关重要的补充提案拿到了一定进展(所以lazy啥时候有人愿意接手…)。遗憾的是,concurrent queue依然在Concurrency TS v3和IS之间举棋不定,进入26的希望已经较为渺茫。除此之外,type_order_v也进入了Stage 3,有望给所有类型一个标准化的偏序关系。

Stage 1工作组方面,SG9 Ranges完成了Range化的并行算法的设计,但是离自己的plan依然差的有点远()。SG21 Contracts在完善Wording的同时,已经渐渐转向post-MVP特性,例如把语言UB大部分转成Contracts。SG23 Security完成了Profile的初版设计,不过前景究竟如何还要看看

展望明年的会议总体情况,“双边反转”已经基本成为事实,20/23周期的末尾都是LWG提案太多完不成不得不扔掉一些,这次LWG已经完成了绝大多数大提案,队伍反而不是很挤。提案太多完不成的变成了CWG,队伍里三个大提案实在有点吃不消,要做好CWG扔掉一堆小提案的准备。

编译器信息最新动态推荐关注hellogcc公众号 本周没更新 点击跳转上周的

文章

Exploring C++ std::span – Part 4: Const-Correctness and Type-Safety

还是介绍span的优缺点。这里介绍一种API冗余的问题

#include <span>
#include <iostream>
#include <vector>
void processData(int* data, std::size_t size) {
    std::cout << "fucked";
}
void processData(std::span<int> data) {
    std::cout << "ok";
}


int main() {
    std::vector<int> v{ 1,3, 5,7, 9};
    processData({v.data(), v.size()});
}

两种接口存在误用可能,保留一个即可

How do I put a non-copyable, non-movable, non-constructible object into a std::optional?

The operations for reading and writing single elements for C++ standard library maps

raymood chen这俩文章是连着的,我就放在一起讲了

一个是如何处理optional构造不能复制/移动的对象,简单来说解决方案就是类似std::elide

看代码 godbolt

#include <vector>
#include <string>
#include <iostream>
#include <optional>

struct Region {
        int dummy[100];
};
struct Widget
{
    Widget(Region const& region) {

    }
    Widget() = delete;
    Widget(Widget const&) = delete;
    Widget(Widget &&) = delete;
    Widget& operator=(Widget const&) = delete;
    Widget& operator=(Widget &&) = delete;

    static Widget CreateInside(Region const& region) {
        std::cout << "inside\n";
        return Widget(region);
    }
    static Widget CreateOutside(Region const& region) {
        std::cout << "outside\n";
        return Widget(region);
    }
private:
    int dummy[100];
};


struct WidgetInsideRegionCreator
{
    WidgetInsideRegionCreator(Region const& region) : m_region(region) {}
    operator Widget() { return Widget::CreateInside(m_region); }
    Region const& m_region;
};

template<typename F>
struct EmplaceHelper
{
    EmplaceHelper(F&& f) : m_f(f) {}
    operator auto() { return m_f(); }
    F& m_f;
};


int main()
{
    Region region;
    // construct with a Widget value
    std::optional<Widget> o0(WidgetInsideRegionCreator(region)); //函数声明我草
    std::optional<Widget> o{WidgetInsideRegionCreator(region)};

    std::optional<Widget> o2;
    // or place a Widget into the optional
    o2.emplace(WidgetInsideRegionCreator(region));
    // construct with a Widget value
    // 为什么这个不被解析成函数声明?
    std::optional<Widget> o3(
        EmplaceHelper([&] {
            return Widget::CreateInside(region);
        }));
    std::optional<Widget> o4;
    // or place a Widget into the optional
    o4.emplace(EmplaceHelper([&] {
            return Widget::CreateInside(region);
        }));
}

简单来说是通过optional的成员构造,从T本身构造,通过wrapper转发给optional隐式构造

第二篇文章是 map插入接口复杂以及带来的构造问题

Operation 操作 Method 方法
Read, throw if missing读取,如果缺失则抛出异常 m.at(key)
Read, allow missing 阅读,允许缺失 m.find(key)
Read, create if missing阅读,如果不存在则创建 m[key]
Write, nop if exists, discard value写入,如果存在则 nop,丢弃值 m.insert({ key, value })
m.emplace(key, value)
Write, nop if exists写入,如果存在则 nop m.emplace(std::piecewise_construct, ...)
m.try_emplace(key, params)
Write, overwrite if exists编写,如果存在则覆盖 m.insert_or_assign(key, value)

这么多接口,想实现如果没有就插入,如果有不要浪费构造这种逻辑,怎么做?

template<typename Map, typename Key, typename... Maker>
auto& ensure(Map&& map, Key&& key, Maker&&... maker)
{
    auto lower = map.lower_bound(key);
    if (lower == map.end() || map.key_comp()(lower->first, key)) {
        lower = map.emplace_hint(lower, std::forward<Key>(key),
            std::invoke(std::forward<Maker>(maker)...));
    }
    return lower->second;
}

auto& ensure_named_widget(std::string const& name)
{
    return ensure(widgets, name,
        [](auto&& name) { return std::make_shared<Widget>(name); },
        name);
}

一坨屎啊,注意到我们转发的这个逻辑,其实就是上面的elide

之前在115期也介绍过 优化insert

insert可能不成功,所以需要推迟value构造,惰性构造

template <class F>
struct lazy_call {
    F f;

    template <class T> operator T() { return f(); }
};

#define LAZY(expr) lazy_call{[&]{ return expr; }}
auto [iter, success] = map.try_emplace(key, LAZY(acquire_value()));

其实就是elide

应该有机会合入,代码也很简单 godbolt

那我们可以用emplacehelper,也就是elide重新实现一下,就用try_emplace就好了

template<typename Map, typename Key, typename... Maker>
auto& ensure(Map&& map, Key&& key, Maker&&... maker)
{
    return *map.try_emplace(key, EmplaceHelper([&] {
        return std::invoke(std::forward<Maker>(maker)...);
    }).first;
}

一行,甚至都不用封装这么丑的代码,直接在用到map的地方直接调用就行,比如

auto& item =
    *widgets.try_emplace(name, EmplaceHelper([&] {
        return std::make_shared<Widget>(name); })).first;

Some Of My Experience About Linking C/C++ On Linux

简单来说就是符号查找的问题

比如不同库可能的符号覆盖,符号weak strong之类的说明,不了解的建议翻翻《链接装载库》,虽然旧也够用

引申阅读 问题排查:C++ exception with description “getrandom“ thrown in the test body

找不到符号 -> 宿主机没提供 编译问题,版本没对上,实现链接了不存在的符号

Retrofitting spatial safety to hundreds of millions of lines of C++

[加固详细介绍](https://l...

Read more

C++ 中文周刊 2024-11-16 第172期

23 Nov 14:49
c8b4250
Compare
Choose a tag to compare

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 点击进入 满了加这俩 729240657 866408334

RSS

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

本期文章由 HNY 赞助 在此表示感谢


资讯

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

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

clang增加了一个safebuffer模式

可以使用 -Wunsafe-buffer-usage 目前还在开发中

clang增加了函数分析能力

在noexcept 基础上增加了noblocking noallocating

更准确分析函数行为,可以配合之前介绍的RealTimeSan使用

主要问题是函数指针不行,函数指针/function自动丢弃上述属性

另外存在属性覆盖,noblocking noallocating的必要条件是noexcept,权限大于,大家懂我意思吧

函数指针怎么绕过

std::sort(vec.begin(), vec.end(),
  [](const Elem& a, const Elem& b) [[clang::nonblocking]] { return a.mem < b.mem; }); // OK

static bool compare_elems(const Elem& a, const Elem& b) [[clang::nonblocking]] {
  return a.mem < b.mem; }; // 不行 属性会丢

std::sort(vec.begin(), vec.end(), compare_elems);

template <typename>
class nonblocking_fp;

template <typename R, typename... Args>
class nonblocking_fp<R(Args...)> {
public:
  using impl_t = R (*)(Args...) [[clang::nonblocking]];

private:
  impl_t mImpl{ nullptr_t };
public:
  nonblocking_fp() = default;
  nonblocking_fp(impl_t f) : mImpl{ f } {}

  R operator()(Args... args) const
  {
    return mImpl(std::forward<Args>(args)...);
  }
};

// deduction guide (like std::function's)
template< class R, class... ArgTypes >
nonblocking_fp( R(*)(ArgTypes...) ) -> nonblocking_fp<R(ArgTypes...)>;

// --

// Wrap the function pointer in a functor which preserves ``nonblocking``.
std::sort(vec.begin(), vec.end(), nonblocking_fp{ compare_elems });

文章

~~我用得着你说?~~强调span不容易用错

template<typename T>
class HasIsNullMethod {
    struct Yes { char unused[1]; };
    struct No { char unused[2]; };
    template <typename U, U u> struct reallyHas;
    template <typename C> static Yes& test(reallyHas<char (C::*)(), &C::isNull>* /*unused*/) { }
    template <typename C> static Yes& test(reallyHas<char(C::*)() const, &C::isNull>* /*unused*/) { }
    // template<class C> static Yes test(char(*)[static_cast<int>(&C::isNull == false) + 1]) {}
    template<class C> static No test(...);
public:
    static const bool Value = (sizeof(test<T>(0)) == sizeof(Yes));
};

struct A {
    char isNull() {}
};
struct B {
    void isNull() {}
};


struct C {
    char isNull;
};

bool fA() {
  return HasIsNullMethod<A>::Value;
}

bool fB() {
    return HasIsNullMethod<B>::Value;
}

bool fC() {
    return HasIsNullMethod<C>::Value;
}
// Type your code here, or load an example.
bool fint()
{
    return HasIsNullMethod<int>::Value;
}
#include <iostream>
int main() {
    std::cout << fint() << "\n";
    std::cout << fA()<< "\n";
    std::cout << fB()<< "\n";
    std::cout << fC()<< "\n";
    return 0;
}

godbolt 不会也没啥,糟粕 喜欢怀旧可以看一下

当实现pimpl惯用法的时候,使用unique_ptr通常因为看不到完整实现(析构)调用失败

作者给的办法是手动加上deleter

不要听他的,直接用shared_ptr,不要多此一举好吧

介绍timezone

我记得有一个date库就做了这个活,用不上可以直接用那个date

简单贴一下代码,介绍一下接口

#include <chrono>
#include <print>

int main() {
    const auto now = std::chrono::system_clock::now();      
    auto zt_local = std::chrono::zoned_time{ std::chrono::current_zone(), now };
    std::print("now is {} UTC and local is: {}\n", now, zt_local);

    constexpr std::string_view Warsaw{ "Europe/Warsaw" };
    constexpr std::string_view NewYork{ "America/New_York" };
    constexpr std::string_view Tokyo{ "Asia/Tokyo" };

    try
    {
        const std::chrono::zoned_time zt_w{Warsaw, now};
        std::print("Warsaw: {0:%F} {0:%R}\n", zt_w);
        const std::chrono::zoned_time zt_ny{NewYork, now};
        std::print("New York: {0:%F} {0:%R}\n", zt_ny);
        const std::chrono::zoned_time zt_t{Tokyo, now};
        std::print("Tokyo: {0:%F} {0:%R}\n", zt_t);
    }
    catch (std::runtime_error& ex)
    {
        std::print("Error: {}", ex.what());
    }
}
/*
now is 2024-11-15 22:31:24.193993753 UTC and local is: 2024-11-15 22:31:24.193993753
Warsaw: 2024-11-15 23:31
New York: 2024-11-15 17:31
Tokyo: 2024-11-16 07:31
*/

godbolt 还有其他代码,就不贴了

这人不懂c++大惊小怪,就是简单的const延长生命周期

不看代码了。单纯喷他一下

老文章,valgrind不如sanitizer直接/快,且有遗漏

死循环优化

#include <iostream>
     
int fermat () {
    const int MAX = 100;
    int a=1,b=1,c=1;
    int iter = 0;
    while (1) {
        if ( (a*a*a) == (b*b*b) + (c*c*c) ) {
            std::cout << "Found!\n";
            return 1;
        }
        a++;
        if (a>MAX) {
            a=1;
            b++;
        }
        if (b>MAX) {
            b=1;
            c++;
        }
        if (c>MAX) {
            c=1;
        }
        ++iter;
    }
    return 0;
}

int main () {
    if (fermat()) {
        std::cout << "Fermat's Last Theorem has been disproved.\n";
    } else {
        std::cout << "Fermat's Last Theorem has not been disproved.\n";
    }
    return 0;
}

打印

Found!
Fermat's Last Theorem has been disproved.

由于return是死循环唯一出口,编译器激进到直接return 1

这个可以看lancern文章有介绍过 感谢zwuis提醒

析构栈溢出一个例子

#include <iostream>
#include <memory>
#include <vector>

struct Node {
    int value = 0;
    std::vector<Node> childrens;
};

struct List {
    int value = 0;
    std::unique_ptr<List> next;

    ~List() {
        while (next) {
            // The destructor is still recursive,
            // but now the recursion depth is 1 call.
            next = std::move(next->next);
        }
    }

    List() noexcept = default;
    List(List&&) noexcept = default;
    List& operator=(List&&) noexcept = default;
};

int main() {
    List dummynode;
    List* l = &dummynode;

    int BOUND = 1000;
    int SUB_BOUND = 100;
    for (int i = 1; i<BOUND; i++) {
        l->value = i;
        l->next = std::make_unique<List>();
        l = l->next.get();
    }
    /*
    // 这个析构会栈溢出
    Node n;
    auto tmp = &n;
    for (int i = 1; i<BOUND; i++) {
        for (int j = 0; j< SUB_BOUND; j++) {
            Node c;
            c.value = j*i;
            tmp->childrens[j] = c;
            // tmp = &tmp->childrens[j]; 
        }
    }
    */
}

noexcept问题 noexcept=noexcept(true), noexcept(cond) 可以自己定制

成员函数除了析构都是noexcept(false) ,如果析构函数抛异常需要显式指明

struct SoBad {
  // invoke std::terminate
  ~SoBad() {
     throw std::runtime_error("so bad dtor");
  }
};

struct  NotSoBad {
  // OK
  ~NotSoBad() noexcept(false) {
    throw std::runtime_error("not so bad dtor");
  }
};

使用noexcept需要你写异常验证代码,避免terminate爆炸

缓冲区溢出问题

一大堆傻逼函数 scanf strcpy strcat gets strncat等等,哦还有memcpy

引申出数组越界问题,数组越界会被激进优化,一定要注意

其实有点和前面的例子很相似

const int N = 10;
int elements[N];

bool contains(int x) {
  for (int i = 0; i <= N; ++i) {
    if (x == elements[i]) {
      return true;
    }
  }
  return false;
}

int main() {
  for (int i = 0; i < N; ++i) {
    std::cin >> elements[i];
  }
  return contains(5);
}

存在越界 -> 越界是UB,编译器认为代码中没有UB,说明越界肯定不可达,说明提前返回 所以直接优化成true

类似例子

const int N = 10;
int main() {
  int decade[N];
  for (int k = 0; k <= N; ++k) {
    printf("k is %d\n",k);
    decade[k] = -1;
  }
}

k越界,越界是UB,编译器认为代码中没有UB,说明永远到不了N,直接死循环

鉴定为不如varint,评论区有人指出性能比varint不太行,测试代码我拿来在7950x WSL跑了一下

2024-11-16T23:09:24+08:00
Running ./benchmarks/msft_proxy_benchmarks
Run on (32 X 4499.92 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x16)
  L1 Instruction 32 KiB (x16)
  L2 Unified 1024 KiB (x16)
  L3 Unified 32768 KiB (x1)
Load Average: 0.65, 0.21, 0.11
---------------------------------------------------------------------------------------
Benchmark                                             Time             CPU   Iterations
---------------------------------------------------------------------------------------
BM_SmallObjectInvocationViaProxy                4648790 ns      5071349 ns          136
BM_SmallObjectInvocationViaVirtualFunction     11986710 ns     13076183 ns           54
BM_SmallObjectInvocationViaVariant              5044399 ns      5495963 ns          127
BM_LargeObjectInvocationViaProxy                7689574 ns      8388641 ns           83
BM_LargeObjectInvocationViaVirtualFunction      9397069 ns     10251350 ns           68
BM_LargeObjectInvocationViaVariant              5046724 ns      5490317 ns          127
BM_SmallObjectManagementWithProxy               1...
Read more

C++ 中文周刊 2024-11-03 第171期

23 Nov 14:49
ad11b49
Compare
Choose a tag to compare

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 点击进入 满了加这俩 729240657 866408334

RSS

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

本期文章由 HNY 赞助 在此表示感谢

本期内容较少,掺杂一些互动


资讯

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

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

文章

How useful is the hint passed to the std::unordered_… collections?

emplace_hit 在有序map有很大作用,但是在无序容器,由于不允许重复,hint基本没啥用

但无序容器存在允许重复值的 multixx,这种场景可以用,一般来说用不着

总结了一个表格

实现 unordered_multixx unordered_xx
是否允许重复
msvc/STL 如果匹配就是用 如果匹配就使用
clang/libcxx 忽略 如果匹配就使用
gcc/libstdc++ (large or fast) 忽略 如果匹配就使用
gcc/libstdc++ (small and slow) 忽略 使用

libstdc++针对不同key的hash有快慢识别,默认是快的 (long double慢) 这里有坑不知道大家记得不

群友mapleFU投稿

之前用 hint 优化过一些有序容器相关的处理( io range 维护什么的),感觉还是挺有用的

工厂函数的几种实现

其实就是static map,可以有多种维护方法

  • 可以利用类来封装,利用宏生成多个static变量构造,来注册到map
  • singleton模版注册也可以,利用模版实例化来调用注册到map 这两种都适合分散写法

这里有个例子 https://www.cnblogs.com/qicosmos/p/5090159.html

我就不贴代码了,脑补一下就有了

  • 直接注册也可以,比如
static std::unordered_map<std::string, OptionTypeInfo>
    lru_cache_options_type_info = {
        {"capacity",
         {offsetof(struct LRUCacheOptions, capacity), OptionType::kSizeT,
          OptionVerificationType::kNormal, OptionTypeFlags::kMutable}},
        {"num_shard_bits",
         {offsetof(struct LRUCacheOptions, num_shard_bits), OptionType::kInt,
          OptionVerificationType::kNormal, OptionTypeFlags::kMutable}},
        {"strict_capacity_limit",
         {offsetof(struct LRUCacheOptions, strict_capacity_limit),
          OptionType::kBoolean, OptionVerificationType::kNormal,
          OptionTypeFlags::kMutable}},
        {"high_pri_pool_ratio",
         {offsetof(struct LRUCacheOptions, high_pri_pool_ratio),
          OptionType::kDouble, OptionVerificationType::kNormal,
          OptionTypeFlags::kMutable}},
        {"low_pri_pool_ratio",
         {offsetof(struct LRUCacheOptions, low_pri_pool_ratio),
          OptionType::kDouble, OptionVerificationType::kNormal,
          OptionTypeFlags::kMutable}},
};

直接注册也未尝不可,直观,适合聚集写法

依赖dlopen也可以,不过属于杀鸡牛刀

如何判断一个数字是不是浮点数0?判断0正负 https://godbolt.org/z/jcqc38qqW

直接贴代码

#include <numeric>
#include <cmath>
#include <iostream>


class FloatingPointComparator {
private:
    static constexpr double DEFAULT_EPSILON = 1e-10;
    static constexpr double MIN_NORMAL = std::numeric_limits<double>::min();
    static constexpr double MAX_NORMAL = std::numeric_limits<double>::max();

public:
    // 基本的零值检查
    static bool isZero(double value) {
        return std::abs(value) < DEFAULT_EPSILON;
    }
    
    // 带自定义误差的零值检查
    static bool isZeroWithEpsilon(double value, double epsilon) {
        return std::abs(value) < epsilon;
    }
    
    // 相对误差检查
    static bool isZeroRelative(double value) {
        if (std::abs(value) < MIN_NORMAL) {
            return true;
        }
        return std::abs(value) < DEFAULT_EPSILON * std::max(1.0, std::abs(value));
    }
    
    // IEEE 754 特殊值检查
    static bool isSpecial(double value) {
        return std::isnan(value) || std::isinf(value);
    }
    
    // 判断是否为正负零
    static bool isExactZero(double value) {
        return value == 0.0 || value == -0.0;
    }
    
    // 综合判断
    static bool isEffectivelyZero(double value) {
        if (isSpecial(value)) {
            return false;
        }
        if (isExactZero(value)) {
            return true;
        }
        return isZeroRelative(value);
    }
};

class ZeroSignChecker {
public:
    static bool isNegativeZero(double value) {
        if (value != 0.0) return false;
         /*
        union {
            double d;
            uint64_t u;
        } u = {value};
        return (u.u >> 63) == 1;
        */   
        auto u = std::bit_cast<std::uint64_t>(value);
        // 检查符号位(最高位)
        return (u >> 63) == 1;
    }
    
    static bool isPositiveZero(double value) {
        if (value != 0.0) return false;
        /*
        union {
            double d;
            uint64_t u;
        } u = {value};
        return (u.u >> 63) == 0;
        */
        auto u = std::bit_cast<std::uint64_t>(value);
        
        // 检查符号位
        return (u >> 63) == 0;
    }
    static bool isPositiveZeroV2(double value) {
        return value == 0.0 && !std::signbit(value);
    }
    
    static bool isNegativeZeroV2(double value) {
        return value == 0.0 && std::signbit(value);
    }

    static bool isNegativeZeroCoreDump(double value) {
        if (value != 0.0) return false;
        return std::isinf(1.0 / value) && (1.0 / value < 0);
    }
    
    static bool isPositiveZeroCoreDump(double value) {
        if (value != 0.0) return false;
        return std::isinf(1.0 / value) && (1.0 / value > 0);
    }
};

// 使用示例
void testZeroSign() {
    double pzero = 0.0;
    double nzero = -0.0;
    
    std::cout << "Positive zero: " << ZeroSignChecker::isPositiveZero(pzero) << std::endl;
    std::cout << "Negative zero: " << ZeroSignChecker::isNegativeZero(nzero) << std::endl;
    std::cout << "Positive zero: " << ZeroSignChecker::isPositiveZeroV2(pzero) << std::endl;
    std::cout << "Negative zero: " << ZeroSignChecker::isNegativeZeroV2(nzero) << std::endl;
}

// 使用示例
void testFloatingPoint() {
    double values[] = {
        0.0,
        -0.0,
        1e-15,
        1e-10,
        std::numeric_limits<double>::min(),
        std::numeric_limits<double>::denorm_min(),
        std::numeric_limits<double>::quiet_NaN(),
        std::numeric_limits<double>::infinity()
    };
    
    for (double val : values) {
        std::cout << "Value: " << val << std::endl;
        std::cout << "Is zero? " << FloatingPointComparator::isEffectivelyZero(val) << std::endl;
        std::cout << "Is special? " << FloatingPointComparator::isSpecial(val) << std::endl;
        std::cout << "Is exact zero? " << FloatingPointComparator::isExactZero(val) << std::endl;
        std::cout << "-------------------" << std::endl;
    }
}

int main() { 
    testFloatingPoint();
    testZeroSign();
    return 0;
}

为什么 exit() 函数不是线程安全的? https://www.zhihu.com/question/2278762213/

主要原因 exit语义等同于从main 返回,会涉及到资源释放等相关流程,自然引入竞争问题

避免全局资源释放,使用quick_exit

另外直接列一下各种exit区别 https://learn.microsoft.com/en-us/previous-versions/6wdz5232(v=vs.140)

  • exit 执行完整的 C 库终止过程,终止进程,并向主机环境提供提供的状态代码。
  • _Exit 执行最少的 C 库终止过程,终止进程,并向主机环境提供提供的状态代码。
  • _exit 执行最少的 C 库终止过程,终止进程,并向主机环境提供提供的状态代码。
  • quick_exit 执行快速 C 库终止过程,终止进程,并向主机环境提供提供的状态代码。
  • _cexit 执行完整的 C 库终止过程并返回给调用方。不终止进程。
  • _c_exit 执行最少的 C 库终止过程并返回给调用方。不终止进程。

VC++中 log10(1e-23f) 向下舍入时,结果错误。如何解决? https://www.zhihu.com/question/1790209844/

msvc的log10f 和gcc libm的log10f行为不一样。大概实现算法有区别

两种结果都是符合标准的,毕竟round

互动环节

熬夜看了英雄联盟S14总决赛,BLG vs T1, 2:3 本来2:1很有机会,但

第四局第五局的faker真的发挥了200%的水平,逆转夺冠

我看的很难受。尤其是赛后很多人踩脸

我不知道为什么全华班追求冠军是一种过错

什么开香槟不谦逊都成了罪过,怎么给自己打气也要批评?

星际韩国包圆,李培楠也努力追求冠军不放弃

街霸日本包圆,曾卓君也努力追求冠军

dota有wings,就算lgd ti10打的气人也是追求过了

唯独lol,我是真无法理解有这么多喜欢狗仗人势的观众

这样的环境,真令人遗憾

也许这次就相当于dota ti8吧 大家记住了水人泼高地记住了on出乱送,然后顺便骂捞批捞底座赛区杂交赛区

真令人遗憾


上一期 下一期

C++ 中文周刊 2024-10-28 第170期

28 Oct 17:22
90d0ab4
Compare
Choose a tag to compare

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 点击进入 满了加这俩 729240657 866408334

RSS

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

本期文章由 HNY 赞助 在此表示感谢


资讯

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

编译器信息最新动态推荐关注hellogcc公众号 本周更新 第277期

文章

On designing Tenseur, A C++ tensor library with lazy evaluation

这个人写了个类似eigen的库,介绍他的设计原理。其实主要是表达式模版,这里举一个例子

比如你有一个数组相加的场景,但想延迟计算

原型

/// @brief class representing a mathematical 3D vector
class Vec : public std::array<double, 3> {
  public:
    using std::array<double, 3>::array; 
    // inherit constructor (C++11)
    // see https://en.cppreference.com/w/cpp/language/using_declaration
};


/// @brief sum 'u' and 'v' into a new instance of Vec
Vec operator+(Vec const &u, Vec const &v) {
    Vec sum;
    for (size_t i = 0; i < u.size(); i++) {
        sum[i] = u[i] + v[i];
    }
    return sum;
}

Vec x = a + b + c 就有点低效了,中间结果优化不掉

我们的想法就是让operator+推迟,比如a+b生成一个VecSum 他本身不实际计算,直到多个VecSum合并成一个VecSum之后再计算

显然这种转发得用CRTP

template <typename E>
class VecExpression {
  public:
    static constexpr bool is_leaf = false;

    double operator[](size_t i) const {
        // Delegation to the actual expression type. This avoids dynamic polymorphism (a.k.a. virtual functions in C++)
        return static_cast<E const&>(*this)[i];
    }
    size_t size() const { return static_cast<E const&>(*this).size(); }
};
class Vec : public VecExpression<Vec> {
    std::array<double, 3> elems;

  public:
    static constexpr bool is_leaf = true;

    decltype(auto) operator[](size_t i) const { return elems[i]; }
    decltype(auto) &operator[](size_t i)      { return elems[i]; }
    size_t size()               const { return elems.size(); }

    // construct Vec using initializer list 
    Vec(std::initializer_list<double> init) {
        std::copy(init.begin(), init.end(), elems.begin());
    }

    // A Vec can be constructed from any VecExpression, forcing its evaluation.
    template <typename E>
    Vec(VecExpression<E> const& expr) {
        for (size_t i = 0; i != expr.size(); ++i) {
            elems[i] = expr[i];
        }
    }
};
template <typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2> > {
  // cref if leaf, copy otherwise
  typename std::conditional<E1::is_leaf, const E1&, const E1>::type _u;
  typename std::conditional<E2::is_leaf, const E2&, const E2>::type _v;

  public:
    static constexpr bool is_leaf = false;

    VecSum(E1 const& u, E2 const& v) : _u(u), _v(v) {
        assert(u.size() == v.size());
    }
    decltype(auto) operator[](size_t i) const { return _u[i] + _v[i]; }
    size_t size()               const { return _v.size(); }
};
  
template <typename E1, typename E2>
VecSum<E1, E2>
operator+(VecExpression<E1> const& u, VecExpression<E2> const& v) {
   return VecSum<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
}

这样 a+b+c 的类型是 VecSum<VecSum<Vec, Vec>, Vec>

Vec x = a + b + c 会调用Vec(VecExpression<E> const& expr)

elems[i] = expr[i];会展开成elems[i] = a.elems[i] + b.elems[i] + c.elems[i]

这样就没有临时Vec对象了

教你 require用法

基本,require concept

template <typename T>
auto debug_output(const T&) { // default implementation
    return "???";
}

template <typename T>
    requires std::integral<T>
auto debug_output(const T& t) {
    // return a range of characters representing the integer value of t
}

template <typename T>
    requires std::floating_point<T>
auto debug_output(const T& t) {
    // return a range of characters representing the floating point value of t
}

用在constexpr里

template <typename Cont, typename Rng>
void cont_assign(Cont& cont, Rng&& rng) {
    cont.clear();

    if constexpr (requires { cont.reserve(std::ranges::size(rng)); }) {
        cont.reserve(std::ranges::size(rng));
    }
    for (auto&& elem : std::forward<Rng>(rng)) {
        cont.push_back(std::forward<decltype(elem)>(elem));
    }
}

requires requires, requires本身就是concept

template <typename T>
    requires requires(const T& t) { t.debug_output(); }
auto debug_output(const T& t) noexcept(noexcept(t.debug_output())) {
    return t.debug_output();
}

requires { requires } 用在constexpr里

template <std::ranges::forward_range Rng>
bool all_same(Rng&& rng) {
    if constexpr (requires { requires tc::constexpr_size<Rng>() <= 1; }) {
        return true;
    } else {
        … // loop as before
    }
}

Heterogeneous lookup in unordered C++ containers

透明查找,避免复制,默认不开,怎么开?看代码

struct stringly_hash
{
  using is_transparent = void;
  [[nodiscard]] size_t operator()(char const* rhs) const
  {
    return std::hash<std::string_view>{}(rhs);
  }
  [[nodiscard]] size_t operator()(std::string_view rhs) const
  {
    return std::hash<std::string_view>{}(rhs);
  }
  [[nodiscard]] size_t operator()(std::string const& rhs) const
  {
    return std::hash<std::string>{}(rhs);
  }
};

template <typename ValueType>
using unordered_string_map = std::unordered_map<
  std::string,
  ValueType,
  stringly_hash,
  std::equal_to<>
>;

咱们说过挺多次了

Zero or sign extend

讨论一种场景,无符号数/有符号数的扩展问题,比如给你一个11位的数字,你给扩展到32位

一种最简单的写法

int32 sign_extend(int32 val_11b) {
    int32 t = val_11b << (32 - 11);
    return t >> (32 - 11);
}

举例

// 假设输入的11位数是: 000 0010 1001 (原始值为41)

int32 t = val_11b << (32 - 11); // 左移21位

// 变成: 0010 1001 0000 0000 0000 0000 0000 0000

return t >> (32 - 11); // 算术右移21位

// 变成: 0000 0000 0000 0000 0000 0000 0010 1001 (仍然为41)

// 如果输入是负数,比如 110 0010 1001 (-215)

// 左移后: 0010 1001 0000 0000 0000 0000 0000 0000

// 算术右移后: 1111 1111 1111 1111 1111 1110 0010 1001 (-215)

>> 移动保留符号,所以这么玩也不会存在问题,但可能依赖数字实现(补码反码问题)

考虑位运算

int sign_extend(int val_11b) {
    return (val_11b & 0x3ff) - (val & 0x400);
}

0x3ff和400哪里来的?0x400是11位, 0x3ff是后10位

举例

// 对于正数 0010 1001 (41):

(41 & 0x3ff) - (41 & 0x400)

= 41 - 0 = 41

// 对于负数 1100 1001 (-215):

(0x329 & 0x3ff) - (0x329 & 0x400)

= 809 - 1024 = -215

当然还有更简洁的

int sign_extend(int val_11b) {
    return val - (val & 0x400) * 2;
}

举例
// 对于正数 0010 1001 (41):

41 - (41 & 0x400) * 2

= 41 - (0) * 2

= 41 - 0

= 41

// 对于负数 1100 1001 (原值 809):

809 - (809 & 0x400) * 2

= 809 - (0x400) * 2

= 809 - 2048

= -215

让我们从11位扩展到任意位(小于32)

int zero_or_sign_extend(int val, int sign_bit) {
    return val - (val & sign_bit) * 2;
}

当然也可以异或

int zero_or_sign_extend(int val, int sign_bit) {
    return (val ^ sign_bit) - sign_bit;
}

举例

// 对于11位正数 0010 1001 (41), sign_bit = 0x400:

(41 ^ 0x400) - 0x400

= 1065 - 1024

= 41

// 对于11位负数 1100 1001 (809), sign_bit = 0x400:

(809 ^ 0x400) - 0x400

= 809 - 1024

= -215

// 对于8位数:
// 正数 0100 0001 (65), sign_bit = 0x80:

(65 ^ 0x80) - 0x80

= 193 - 128

= 65

// 负数 1100 0001 (193), sign_bit = 0x80:

(193 ^ 0x80) - 0x80

= 65 - 128

= -63

Inserting a 0 bit in the middle of a value

这个背景可以不提,简单说就是给一个二进制中间差一个0,很妙的办法,直觉来说怎么写?

首先给位置分两段,低位不变,高位置移动一位,然后拼起来,对不对

uint64 insert_zero_bit(uint64 value, int pos) {
    uint64 bottom_mask = (1u64 << pos) - 1;
    uint64 top_mask = ~bottom_mask;

    uint64 bottom_bits = value & bottom_mask;
    uint64 top_bits = value & top_mask;
    return bottom_bits | (top_bits << 1);
}

代码也很直观,咱们拿一个例子带入一下

假如 1 0 1 1 0 1 0 1 -> 1 0 1 1 0 0 1 0 1

第四位插个0 pos=3

首先拿到bottom_mask 0 0 0 0 0 0 0 1左移3位减一 0 0 0 0 0 1 1 1

top mask就是 1 1 1 1 1 0 0 0

bottom_bits就是 1 0 1 1 0 1 0 1 保留后三位 0 0 0 0 0 1 0 1

top_bits 就是1 0 1 1 0 1 0 1 保留前五位 1 0 1 1 0 0 0 0

然后top bit左移一位组合1 0 1 1 0 0 0 0 0 | 0 0 0 0 0 1 0 1 -> 1 0 1 1 0 0 1 0 1

这里提到了一个优化的写法

我们要做的就是高位移动一位,就不要考虑低位了,还要算来算去

最简单的移动方法就是自己加自己对不对? 1 + 1 -> 10

那我高位加自己不就解决了?

这也就是优化算法的原理

uint64 insert_zero_bit(uint64 value, int pos) {
    uint64 top_mask = ~0u64 << pos;
    return value + (value & top_mask);
}

首先我们找到高位,然后高位相加等于移动一位,然后低位没加,不变

很巧妙的思路,就是不能一次性加多个0,得一点一点加

当然同理,我们可以弄一个去掉0 的算法

uint64 remove_zero_bit(uint64 value, int pos) {
    uint64 top_mask = ~0u64 << pos;
    return value - ((value & top_mask) >> 1);
}

注意我们知道指定位置是0,所以移动没啥影响,如果指定去除位置的值,就不能这么简单的算了

一切的前提是知道指定pos是0 的前提下展开的

Triaging clang C++ frontend bugs

教你处理clang前端bug/修复指引

Implementing Trivial Relocation in Library

介绍trivial relocation基于反射的实现。复杂。看一乐,这个之前提到过,是两个提案实现方法不同,一直在吵架

Placeholder substitution in the preprocessor

用宏实现了magic enum类似手法。这拖代码屎一样 godbolt

开源项目介绍

  • asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群753302367和作者对线
  • endianint 一个endian库,代码很短

[上一期...

Read more