C++ 中文周刊 第22期

reddit/hackernews/lobsters/meetingcpp摘抄一些c++动态。

每周更新

周刊项目地址 github在线地址知乎专栏

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


资讯

编译器信息最新动态推荐关注hellogcc公众号

本周周报github直达

文章

这个标签两个作用,1,直接干掉空基类优化,直接看官方例子https://eel.is/c++draft/dcl.attr.nouniqueaddr

template<typename Key, typename Value,
         typename Hash, typename Pred, typename Allocator>
class hash_map {
  [[no_unique_address]] Hash hasher;
  [[no_unique_address]] Pred pred;
  [[no_unique_address]] Allocator alloc;
  Bucket *buckets;
  // ...
public:
  // ...
};

如果hash pred allocator都是空的,那和bucket共享同一个地址

第二个例子 https://godbolt.org/z/3r8hq5sxe

看傻了,居然只占1byte

#include <cassert>
#include <cstdint>
#include <algorithm>
#include <type_traits>

template<auto Size>
struct fixed_string {
  char data[Size + 1]{};
  static constexpr auto size = Size;
  constexpr explicit(false) fixed_string(char const* str) { std::copy_n(str, Size + 1, data); }
  constexpr explicit(false) operator std::string_view() const { return {data, Size}; }
};
template<auto Size> fixed_string(char const (&)[Size]) -> fixed_string<Size - 1>;

template<class T, fixed_string Str, auto Offset = 0>
struct field {
  constexpr const auto& get() const { return *reinterpret_cast<const T*>(reinterpret_cast<const char*>(this) + Offset); }
  constexpr operator const T&() const { return get(); }
};

enum class side : std::uint8_t { buy, sell };
struct trade {
  [[no_unique_address]] field<::std::int32_t, "price"> price;
  [[no_unique_address]] field<::std::uint32_t, "size", sizeof(std::int32_t)> quantity;
  [[no_unique_address]] field<::side, "side", sizeof(std::int32_t) + sizeof(std::uint32_t)> side;
};
static_assert(1 == sizeof(trade));

auto parse(const void* msg) {
  const auto& trade = *reinterpret_cast<const ::trade*>(msg);
  assert(42 == trade.price);
  assert(100 == trade.quantity);
  assert(side::sell == trade.side);
}

int main() {
  struct [[gnu::packed]] {
    ::std::int32_t price{42};
    ::std::uint32_t quantity{100};
    ::side side{side::sell};
  } trade{};

  parse(std::addressof(trade));
}

但实际上trade类不是用来初始化的,是靠trade当做一个轻量的带类型的盒子,来解析POD类型,这和POD指针有啥区别?栈空间也不用省啊

介绍futex_wait以及google补丁的提升,不过并没有合进内核

一个汇编教程,挺简单,这里直接总结下

基本语法

堆操作

push value ; pushes a value into the stack (decrements ESP by 4, the size of one stack ‘unit’).
pop register ; pops a value to a register (increments ESP by 4).

数据赋值

mov destination, source ; moves copies a value from/to a register.
mov destination, [expression] ; copies a value from a memory address resolved from a ‘register expression’ (single register or arithmetic expression involving one or more registers) into a register.

控制流

jmp destination ; jumps into a code location (sets EIP (instruction pointer)).
jz/je destination ; jumps into a code location if ZF (the zero flag) is set.
jnz/jne destination ; jumps into a code location if ZF is not set.

操作

cmp operand1, operand2 ; compares the 2 operands and sets ZF if they’re equal.
add operand1, operand2 ; operand1 += operand2;
sub operand1, operand2 ; operand1 -= operand2;

函数转移

call function ; calls a function (pushes current EIP, then jumps to the function).
retn ; returns to caller function (pops back the previous EIP).

汇编模式

函数开头

55          push    ebp        ; preserve caller function's base pointer in stack
8B EC       mov     ebp, esp   ; caller function's stack pointer becomes base pointer (new stack frame)
83 EC XX    sub     esp, X     ; adjust the stack pointer by X bytes to reserve space for local variables

函数结尾

8B E5    mov    esp, ebp    ; restore caller function's stack pointer (current base pointer) 
5D       pop    ebp         ; restore base pointer from the stack
C3       retn               ; return to caller function

函数调用func(1, 2, 3);

6A 03             push    3
6A 02             push    2
6A 01             push    1
E8 XX XX XX XX    call    func

下面开始组合

if语句

void print_equal(int a, int b) {
    if (a == b) {
        printf("equal");
    }
    else {
        printf("nah");
    }
}
void __cdecl print_equal(int, int):

     10000000   55                push   ebp
     10000001   8B EC             mov    ebp, esp
     10000003   8B 45 08          mov    eax, [ebp+8]       ; load 1st argument
     10000006   3B 45 0C          cmp    eax, [ebp+0Ch]     ; compare it with 2nd
  ┌┅ 10000009   75 0F             jnz    short loc_1000001A ; jump if not equal
  ┊  1000000B   68 94 67 00 10    push   offset aEqual  ; "equal"
  ┊  10000010   E8 DB F8 FF FF    call   _printf
  ┊  10000015   83 C4 04          add    esp, 4
┌─┊─ 10000018   EB 0D             jmp    short loc_10000027
│ ┊
│ └ loc_1000001A:
│    1000001A   68 9C 67 00 10    push   offset aNah    ; "nah"
│    1000001F   E8 CC F8 FF FF    call   _printf
│    10000024   83 C4 04          add    esp, 4
│
└── loc_10000027:
     10000027   5D                pop    ebp
     10000028   C3                retn

用宏来元编程的。不能细看,脑壳疼,有喜欢的,仓库在这里

这个文章从一个简单的算法开始,介绍各种库的api设计

// [[pre: alpha >= -1. && alpha <= 1. ]]
// [[pre: 11 <= beta && beta < 247 ]]
// [[post ret: ret >= 0. && ret <= 1.]]
auto noise(
    float input,
    float alpha,
    int beta)
{
  // set of extremely complex operations involving the inputs to the algorithm
  return std::fmod(std::pow(input, alpha), float(beta)) / beta;
}

这个算法这些参数就很难用,首先类型就很模糊,弄错参数 位置可能结果就完全不一样

在考虑各种api调用交互,可能更不好用,

// noise.hpp
#pragma once
struct noise
{
  float alpha;
  int beta;

  /* constexpr_in_some_future_standard */
  float operator()(float input) const
  {
    return std::fmod(std::pow(input, alpha), float(beta)) / beta;
  }
};

首先让noise输入简单一点,然后考虑引入反射。比较复杂,但是点子比较新颖。

复习一下函数指针以及对应的汇编

#include <iostream>
struct greater {
    bool operator()(int a, int b) {
        return a > b;
    }
};
int main() {
    struct greater obj;
    std::cout << obj(3, 4);
}
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        lea     rax, [rbp-1]
        mov     edx, 4
        mov     esi, 3
        mov     rdi, rax
        call    greater::operator()(int, int)
        movzx   eax, al
        mov     esi, eax
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(bool)
        mov     eax, 0
        leave
        ret

激进一点 call都可以优化掉

这里讲c++17的一个特性

这段代码在c++17遍不过

template<typename Derived>
struct Base
{
private:
    Base(){};
    //friend int main(); //加上这行就能编译过
};

struct Derived : Base<Derived>
{
};

int main()
{
    Derived d{};
    //Derived d; //或者去掉大括号也能编译过
}

主要是触发了这个Extened Aggergate Initialisation

struct X
{
    int a;
    int b;
    int c;
};

struct Y : X
{
    int d;
};
//可以这样构造
Y y{1, 2, 3, 4};
//还可以这样
Y y{ {1, 2, 3}, 4};
//还可以这样
Y y{ {}, 4 };
//还可以这样
Y y{ {1}, 4};
//不可以这样
Y y{4};

上面编译不过的代码隐含的调用了基类构造,但是是private的,要么去掉括号避免,要么加上friend让构造可见

视频

从他的一个小项目入手,怎么提高代码速度。没仔细看

还是介绍他的colony库,优化过的vector类似的容器。16年就在宣传

项目


本文永久链接

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