C++ 中文周刊 2025-04-30 第182期

周刊项目地址

公众号

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

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

RSS

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

本文感谢选择公理赞助,祝您身体健康万事如意


资讯

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

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

性能周刊

文章

On the Ignorability of Attributes

作者讲的是属性的可忽略性质导致和关键字不协调,比如[[deprecated]], [[nodiscard]] 为什么不是关键字,但constinit是

又比如override可以忽略,但他不是属性,是关键字

c++26引入了trivially_relocatable_if_eligible和replaceable_if_eligible两个关键字,按理说他应该类似属性这种可忽略,保持向后兼容,但不是

属性就不应该可忽略,这样能让这些奇怪关键字回归属性来用

大家有什么见解?我觉得确实有点乱,对付用的水平,什么属性,我直接一个宏代替

Unsigned comparisons using signed types

使用有符号类型来比较无符号数

考虑溢出加法

如果将最小可能值(非常小的负数值)M与有符号整数x和y相加,则(x + M) < (y + M)等同于将x和y视为无符号整数值进行比较。

原理:

将0映射到M(0成为最小值), 将所有正值映射到范围从M+1到-1(正值变为负值), 负值都会溢出,使最后一位变为零,而其他位保持不变,从而将负值从M到-1映射到范围0到-M-1(负值变为正值)

考虑避免c++溢出问题,使用异或,因为 x + M = (x^M) + ((x&M)«1) 后半部分无符号数就是0,所以直接比较异或 (x ^ M) < (y ^ M)

Growing Buffers to Avoid Copying Data

主要讲的mmap/xmalloc/realloc/VirtualAlloc延长空间分配行为。没啥值得看的

Programming languages should have a tree traversal primitive

挺有意思,我直接贴代码了 https://godbolt.org/z/44f9eM41W

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

enum class _tree_return {
    Continue = 0,
    Prune,
    Break
};

template<typename T, typename F1, typename F2, typename F3>
_tree_return _for_tree(T initial, F1 condition, F2 branch, F3 visit) {
    _tree_return result = visit(initial);
    if(result == _tree_return::Break) return _tree_return::Break;

    if(result != _tree_return::Prune) {
        for(T subnode : branch(initial)) {
            if(condition(subnode)) {
                _tree_return result = _for_tree(subnode, condition, branch, visit);
                if(result == _tree_return::Break) return _tree_return::Break;
            }
        }
    }
    return _tree_return::Continue;
}

#define tree_break return _tree_return::Break
#define tree_prune return _tree_return::Prune
#define tree_continue return _tree_return::Continue

                                                          //v-- semicolon to not allow you to get the return value here
#define for_tree(XName, Xinitial, Condition, Branch, Visit) ;_for_tree(Xinitial, \
[&](decltype(Xinitial) XName){ return Condition; }, \
[&](decltype(Xinitial) XName){ return std::vector<decltype(Xinitial)>Branch; }, \
[&](decltype(Xinitial) XName){ Visit; return _tree_return::Continue; })
//excuse the use of a std::vector in there, I guess you cant return an initialize_list from a lambda
//that wouldn't really be an issue if this was implemented at the language level instead of hacked together from lambdas and macros

struct Node {
    Node* left = NULL;
    Node* right = NULL;
    std::string value;
    Node(std::string value):value(value){}
};

int main() {
    //syntax is a little uglier than it could be if it was native
    
    //imperative tree sample
    for_tree(x, std::string(""), x.size()<=3, ({x+"a", x+"b", x+"c"}), {
        std::cout << x << std::endl;
    });
    
    //tree structure sample
    Node mytree("root");
    mytree.left = new Node("left");
    mytree.right = new Node("right");
    mytree.left->left = new Node("leftleft");
    
    for_tree(x, &mytree, x != NULL, ({x->left, x->right}), {
        std::cout << x->value << std::endl;
    });

    return 0;
}

The correct way to do type punning in C++ - The second act

struct ConfigValues {
  uint32_t                  chksum;
  std::array<uint32_t, 128> values;
};

bool ProcessData(std::span<unsigned char> bytes)
{
  if(bytes.size() < sizeof(ConfigValues)) { return false; }

  //UB 类型双关
  ConfigValues* cfgValues = reinterpret_cast<ConfigValues*>(bytes.data());

  return HandleConfigValues(cfgValues);
}

用bit_cast复制一份可能开销太大,怎么办?

struct ConfigValues {
  uint32_t                  chksum;
  std::array<uint32_t, 128> values;
};

bool ProcessData(std::span<unsigned char> bytes)
{
  if(bytes.size() < sizeof(ConfigValues)) { return false; }

  A Using std::start_lifetime_as
  ConfigValues* cfgValues = std::start_lifetime_as<ConfigValues>(bytes.data());

  return HandleConfigValues(cfgValues);
}

6 usability improvements in GCC 15

注释错误更好看,模版报错格式化,输出报告SARIF更文档化,可折叠, 默认-std=gnu23,以及提供gdiagnostic api(c,py)方便分析报告

总算现代了点

Detect control characters, quotes and backslashes efficiently using ‘SWAR’

使用SWAR高效检测控制字符(小于32)、引号和反斜杠

直接贴代码,看变量名字就能懂

bool has_json_escapable_byte(uint64_t x) {
    uint64_t is_ascii = 0x8080808080808080ULL & ~x;
    uint64_t lt32 =
        (x - 0x2020202020202020ULL);
    uint64_t sub34 = x ^ 0x2222222222222222ULL;
    uint64_t eq34 = (sub34 - 0x0101010101010101ULL);
    uint64_t sub92 = x ^ 0x5C5C5C5C5C5C5C5CULL;
    uint64_t eq92 = (sub92 - 0x0101010101010101ULL);
    return ((lt32 | eq34 | eq92) & is_ascii) != 0;
}

优化版本

bool has_json_escapable_byte(uint64_t x) {
    uint64_t is_ascii = 0x8080808080808080ULL & ~x;
    uint64_t xor2 = x ^ 0x0202020202020202ULL;
    uint64_t lt32_or_eq34 = xor2  0x2121212121212121ULL;
    uint64_t sub92 = x ^ 0x5C5C5C5C5C5C5C5CULL;
    uint64_t eq92 = (sub92 - 0x0101010101010101ULL);
    return ((lt32_or_eq34 | eq92) & is_ascii) != 0;
}

合并了32/34计算

Beware when moving a std::optional!

场景是你想掏空optional

template<typename T>
T do_something() {
    std::optional<T> opt = some_oracle<T>();

    if (!opt) {
        std::cerr << "Something terrible happened\n";
        std::exit(EXIT_FAILURE);
    }

    return *opt; // equivalent to .value(), but doesn't throw an exception
}

这样写会复制,这也是常见的用optional错误

比如大家会这样写

std::optional<T> opt = some_good_oracle<T>(); /* assume opt.has_value() */

if (opt.has_value())
    f1( std::move(opt.value()) ); // move the value to avoid copying
                                  // from here onwards opt doesn't have a value

if (opt.has_value())  // true, unexpected!
    f2( std::move(opt.value()) ); // move again!
                                  // in reality f2 got an empty/garbage T

容易用错,我们需要掏空optional

// Bad
// auto x = std::move(opt.value());

// Good
auto x = std::move(opt).value();

std::variant std::pair std::tuple std::any std::expected std::optional 使用注意事项

接着上一个,move这些类型,非常容易用错, 代码例子如下

#include <format>
#include <exception>
#include <variant>
#include <iostream>

class LifeTime {
	int a_ = 0;
	public:
    LifeTime(int a):a_(a){
        std::cout << "LifeTime(): " << a_ << std::endl;
    }

    ~LifeTime() {
        std::cout << "~LifeTime(): " << a_ << std::endl;
    }

	LifeTime(const LifeTime&) {
        std::cout << "LifeTime(const LifeTime&): " << a_ << std::endl;
    }

	LifeTime(LifeTime&& other) noexcept {
		this->a_ = other.a_;
        std::cout << "LifeTime(LifeTime&&): " << a_ << std::endl;
    }

	LifeTime& operator=(const LifeTime&) {
        std::cout << "LifeTime& operator=(const LifeTime&): " << a_ << std::endl;
        return *this;
    }

	LifeTime& operator=(LifeTime&&) noexcept {
        std::cout << "LifeTime& operator=(LifeTime&&): " << a_ << std::endl;
        return *this;
    }
};

//break RVO
std::optional<LifeTime> create_v1() {
	LifeTime a{ 1 };
	return a;
}

//break RVO
std::optional<LifeTime> create_v2() {
	return LifeTime{2};
}

//break RVO
std::optional<LifeTime> create_v3() {
	std::optional<LifeTime> opt;
	opt = LifeTime{ 3 };
	return opt;
}

std::optional<LifeTime> create_v4() {
	std::optional<LifeTime> opt;
	opt.emplace(4);
	return opt;
}

std::optional<LifeTime> create_v5() {
	return std::optional<LifeTime>{5};
}

std::optional<LifeTime> create_v6() {
	return std::optional<LifeTime>{std::in_place, 6};
}

Exploiting Undefined Behavior in C/C++ Programs for Optimization: A Study on the Performance Impact

说大部分依靠UB的优化都可以通过编译器优化/LTO捡回来。只能说仁者见仁吧。

llvm ir级别重写dynamiccast带来收益,效果堪比dyn_cast。但使用条件苛刻,继承必须树形结构

核心思想就是既然是树形结构那就拍扁到一个vtable上,大家用地址+offset比较,跳过typeinfo比较

看一乐,有点思路借鉴

AoS vs SoA in practice: particle simulation

数组结构体怎么改成结构体数组,利用tuple,我照着他的思路抄了一个,很有意思 https://godbolt.org/z/8PGP3n58q

#include <vector>

template <size_t...>
struct IndexSequence
{
};

//#if __has_builtin(__type_pack_element)

////////////////////////////////////////////////////////////
#define SFML_BASE_TYPE_PACK_ELEMENT(N, ...) __type_pack_element<N, __VA_ARGS__>



//#if __has_builtin(__integer_pack)

////////////////////////////////////////////////////////////
#define SFML_BASE_MAKE_INDEX_SEQUENCE(N) IndexSequence<__integer_pack(N)...>
#define SFML_BASE_FORWARD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__)
#define SFML_BASE_REMOVE_REFERENCE(...) __remove_reference(__VA_ARGS__)
#define SFML_BASE_MOVE(...) static_cast<SFML_BASE_REMOVE_REFERENCE(decltype(__VA_ARGS__)) &&>(__VA_ARGS__)

////////////////////////////////////////////////////////////
template <size_t I, typename T>
struct SoABase
{
    ////////////////////////////////////////////////////////////
    enum : size_t
    {
        index = I
    };

    ////////////////////////////////////////////////////////////
    [[no_unique_address]] std::vector<T> data;
};

////////////////////////////////////////////////////////////
template <typename, typename...>
class SoA;

////////////////////////////////////////////////////////////
// NOLINTNEXTLINE(bugprone-macro-parentheses)
#define SOA_AS_BASE(I)       static_cast<SoABase<I, SFML_BASE_TYPE_PACK_ELEMENT(I, Ts...)>&>(*this)
#define SOA_AS_CONST_BASE(I) static_cast<const SoABase<I, SFML_BASE_TYPE_PACK_ELEMENT(I, Ts...)>&>(*this)
#define SOA_ALL_BASES()      static_cast<SoABase<Is, Ts>&>(*this)

////////////////////////////////////////////////////////////
template <size_t... Is, typename... Ts>
class SoA<IndexSequence<Is...>, Ts...> : private SoABase<Is, Ts>...
{
public:
    ////////////////////////////////////////////////////////////
    [[gnu::always_inline]] void clear()
    {
        (SOA_ALL_BASES().data.clear(), ...);
    }

    ////////////////////////////////////////////////////////////
    [[gnu::always_inline]] void reserve(size_t capacity)
    {
        (SOA_ALL_BASES().data.reserve(capacity), ...);
    }

    ////////////////////////////////////////////////////////////
    [[gnu::always_inline]] void resize(size_t size)
    {
        (SOA_ALL_BASES().data.resize(size), ...);
    }

    ////////////////////////////////////////////////////////////
    [[gnu::always_inline]] void push_back(auto&&... values)
    {
        (SOA_ALL_BASES().data.push_back(SFML_BASE_FORWARD(values)), ...);
    }

    ////////////////////////////////////////////////////////////
    [[nodiscard, gnu::always_inline, gnu::pure]] size_t getSize() const
    {
        return SOA_AS_CONST_BASE(0).data.size();
    }

    ////////////////////////////////////////////////////////////
    template <size_t... Js>
    [[gnu::always_inline]] void withNth(size_t i, auto&& f)
    {
        f(SOA_AS_BASE(Js).data[i]...);
    }

    ////////////////////////////////////////////////////////////
    [[gnu::always_inline]] void withAllNth(size_t i, auto&& f)
    {
        f(SOA_ALL_BASES().data[i]...);
    }

    ////////////////////////////////////////////////////////////
    template <size_t... Js>
    [[gnu::always_inline]] void withSubRange(size_t start, size_t end, auto&& f)
    {
        for (size_t i = start; i < end; ++i)
            f(SOA_AS_BASE(Js).data[i]...);
    }

    ////////////////////////////////////////////////////////////
    [[gnu::always_inline]] void withAllSubRange(size_t start, size_t end, auto&& f)
    {
        for (size_t i = start; i < end; ++i)
            f(SOA_ALL_BASES().data[i]...);
    }

    ////////////////////////////////////////////////////////////
    template <size_t... Js>
    [[gnu::always_inline]] void with(auto&& f)
    {
        for (size_t i = 0u; i < getSize(); ++i)
            f(SOA_AS_BASE(Js).data[i]...);
    }

    ////////////////////////////////////////////////////////////
    [[gnu::always_inline]] void withAll(auto&& f)
    {
        for (size_t i = 0u; i < getSize(); ++i)
            f(SOA_ALL_BASES().data[i]...);
    }

    ////////////////////////////////////////////////////////////
    template <size_t... Js>
    void eraseIfByShifting(auto&& f)
    {
        const size_t n = getSize();

        // Find the first element to remove.
        size_t i = 0;
        while (i < n && !f(SOA_AS_BASE(Js).data[i]...))
            ++i;

        // For the remaining elements, shift over those that must be kept.
        size_t newSize = i;

        for (; i < n; ++i)
        {
            if (f(SOA_AS_BASE(Js).data[i]...))
                continue;

            if (newSize != i)
                ((SOA_ALL_BASES().data[newSize] = SFML_BASE_MOVE(SOA_ALL_BASES().data[i])), ...);

            ++newSize;
        }

        // Resize all columns to the new size.
        ((SOA_ALL_BASES().data.resize(newSize)), ...);
    }

    ////////////////////////////////////////////////////////////
    template <size_t... Js>
    void eraseIfBySwapping(auto&& f)
    {
        size_t n = getSize();
        size_t i = 0;

        // Process elements, swapping out removed ones.
        while (i < n)
        {
            if (!f(static_cast<SoABase<Js, SFML_BASE_TYPE_PACK_ELEMENT(Js, Ts...)>&>(*this).data[i]...))
            {
                ++i;
                continue;
            }

            // Swap the current element with the last one, then reduce the container size.
            --n;
            ((std::swap(SOA_ALL_BASES().data[i], SOA_ALL_BASES().data[n])), ...);

            // Do not increment i; check the new element at i.
        }

        // Resize all columns to the new size.
        ((SOA_ALL_BASES().data.resize(n)), ...);
    }
};

////////////////////////////////////////////////////////////
#undef SOA_ALL_BASES
#undef SOA_AS_CONST_BASE
#undef SOA_AS_BASE

////////////////////////////////////////////////////////////
template <typename... Ts>
using SoAFor = SoA<SFML_BASE_MAKE_INDEX_SEQUENCE(sizeof...(Ts)), Ts...>;

#include <string>
#include <iostream>
int main() {
    SoAFor<float,  int, std::string> dummy;
    dummy.push_back(1.1, 2, "33");
    dummy.with<0, 1>(
    [](float& vel, int& acc) { vel += acc; });
    dummy.withAll(
    [](float v, int& a, std::string& s) { std::cout << v << a << s; });
}

Views as Data Members for Custom Iterators

问题:实现一个TwoDIterator 代码脚手架

int main() {
    std::vector<std::vector<int>> input = {
        {1, 2}, {3}, {}, {4, 5, 6}};
    TwoDIterator it(input);

    while (it.has_next())
        std::print("{} ", it.next());
    // 1, 2, 3, 4, 5, 6
}

range大神这时候就说了,一行秒了

auto flat = nested | std::views::join;

常规解法就是先判断每行结束没,每行结束了就整体移动一下,两个index

#include <vector>
#include <stdexcept>
#include <print>

class TwoDIterator {
public:
    using Vec2D = std::vector<std::vector<int>>;

    TwoDIterator(const Vec2D& data) 
    : data_(data), outer_(0), inner_(0) {
        advance_to_next_valid();
    }

    bool has_next() const {
        return outer_ < data_.size();
    }

    int next() {
        if (!has_next()) throw std::out_of_range("No more elements");

        int val = data_[outer_][inner_];
        ++inner_;
        advance_to_next_valid();
        return val;
    }

private:
    void advance_to_next_valid() {
        while (outer_ < data_.size() && inner_ >= data_[outer_].size()) {
            ++outer_;
            inner_ = 0;
        }
    }

    const Vec2D& data_;
    size_t outer_, inner_;
};

怎么组合一下range?

很巧,join_view是有iter的

#include <vector>
#include <stdexcept>
#include <print>
#include <ranges>

class TwoDIteratorJoin {  
public:  
    using Vec2D = std::vector<std::vector<int>>;  
    using JoinView = std::ranges::join_view<std::ranges::ref_view<const Vec2D>>;  
    using Iterator = std::ranges::iterator_t<JoinView>;  
  
    explicit TwoDIteratorJoin(const Vec2D& data)  
        : flattened_(data | std::views::join),  
          iter_(flattened_.begin()),  
          end_(flattened_.end()) {}  
  
    [[nodiscard]] bool has_next() const {  
        return iter_ != end_;  
    }  
  
    [[nodiscard]] int next() {  
        if (!has_next()) throw std::out_of_range("No more elements");  
        return *iter_++;  
    }  
  
private:  
    JoinView flattened_;  
    Iterator iter_, end_;  
};

range大神赢了

Llama’s Paradox - Delving deep into Llama.cpp and exploiting Llama.cpp’s Heap Maze, from Heap-Overflow to Remote-Code Execution.

分析llama.cpp的溢出漏洞。文章很长,我没有耐心看完。安全从业人员感兴趣的自己看一下

开源项目介绍

import shorty;
using namespace shorty::literals;

// you no longer need to remember if it's `std::less` or `std::greater`
std::ranges::sort(subject, $lhs > $rhs);

// filter only even
subject | std::views::filter(($i % 2) == 0);

// zip together and transform
std::views::zip(A,B,C,D) | std::views::transform($a * $b + $c * $d);

// call external function
auto pythagorean = $<sqrt>($a * $a + $b * $b); // or $call<sqrt>;
pythagorean(3.0, 4.0) == 5.0;

// casting
auto to_int = $<int>($0); // or $cast<int>;
to_int(4.3) == 4; 

// remap by index
auto indices = std::vector<int>{...};
auto mapping = indices | $(data)[$i];

有点意思

互动环节

放假了家人们


上一期

本期

下一期

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