Skip to content

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

Compare
Choose a tag to compare
@wanghenshui wanghenshui released this 23 Nov 14:49
c8b4250

周刊项目地址

公众号

点击「查看原文」跳转到 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               1603751 ns      1749509 ns          402
BM_SmallObjectManagementWithUniquePtr           8952997 ns      9766806 ns           72
BM_SmallObjectManagementWithSharedPtr          11484836 ns     12528993 ns           56
BM_SmallObjectManagementWithSharedPtr_Pooled   14436982 ns     15749468 ns           44
BM_SmallObjectManagementWithAny                 6424830 ns      7008942 ns           96
BM_SmallObjectManagementWithVariant              514705 ns       561491 ns         1199
BM_LargeObjectManagementWithProxy              39497657 ns     43086918 ns           17
BM_LargeObjectManagementWithProxy_Pooled       30985407 ns     33760433 ns           21
BM_LargeObjectManagementWithUniquePtr          40788280 ns     44496508 ns           13
BM_LargeObjectManagementWithSharedPtr          50875412 ns     55500655 ns           11
BM_LargeObjectManagementWithSharedPtr_Pooled   30422979 ns     33188319 ns           21
BM_LargeObjectManagementWithAny                32133635 ns     35037580 ns           20
BM_LargeObjectManagementWithVariant            12639262 ns     13788144 ns           50

这明显不如varint啊。还是观望吧

如何显式指定构造函数模板形参?怎样让模板构造函数识别不同类型

实际上就是让构造函数作为工厂函数接口,工厂模式大家都熟悉,但是问题在于构造函数不能指定类型

比如这种实例

// Assume derived classes by convention have a constructor
// whose first parameter is an ObjectManager&.
struct CommonBase
{
    virtual ~CommonBase(){}
    virtual void initialize(int reason) = 0;
};

struct Widget : CommonBase
{
    Widget(int param) {}
    Widget(Widget&&) {}
    void initialize(int reason) {
        std::cout << "ok\n";
    }
     
};

struct ObjectManager
{
    // Concrete should derive from CommonBase
    template<typename Concrete, typename...Args>
    ObjectManager(int reason,
        std::in_place_type_t<Concrete>,
        Args&&...args) :
        m_base(std::make_unique<Concrete>(
                 std::forward<Args>(args)...))
    {
        m_base->initialize(reason);
    }
    template<typename Concrete, typename...Args>        
    static ObjectManager make(int reason, Args&&...args)
    {                                                   
        return ObjectManager(reason,                    
            std::in_place_type_t<Concrete>{},               
            std::forward<Args>(args)...);              
    }  
    std::unique_ptr<CommonBase> m_base;
};

struct ObjectManager0
{
    // Concrete should derive from CommonBase
    template<typename Concrete, typename...Args>
    ObjectManager0(int reason, Args&&...args) :
        m_base(std::make_unique<Concrete>( std::forward<Args>(args)...))
    {
        m_base->initialize(reason);
    }

    std::unique_ptr<CommonBase> m_base;
};

// auto m0 = ObjectManager0<Widget>(9,42);
// auto m0 = ObjectManager0::ObjectManager0<Widget>(9,42);
auto m2 = ObjectManager::make<Widget>(9, 42);

make显然就是工厂函数那种类型构造,但是构造函数没法指定类型

我们怎么给构造函数传递类型?用in_place_type_t

上面的代码还算容易看懂,其实就是tag把类型带过来,有点像identity_type那种玩法

不过构造函数模板不如类模板来的直观一些,这么玩有点复杂, 代码 godbolt

数组长度命名,纠结老半天,了解历史可以看 (没有看的必要)

数组 + 循环index,单线程。玩具,代码 自己看吧。懒得贴了。不值一看

介绍一些代码测试经验,测试数据从何而来,如何更准确的测试接口,如何让测试代码更好的解释函数

感觉做图形学的哥们值得讲一下对应经验

开源项目介绍

  • asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群753302367和作者对线
  • spdlog 1.15 更新,修了一堆bug,没啥重大修复,升不升都行

热门库最近更新了什么

brpc 最近的MR

介绍一下背景

brpc内部是由bthread驱动事件,可以理解为小的线程,类似boost fiber

bthread的调度是wait free的,设计了work steal,如果没有任务就去其他worker线程去偷

问题在于bthread本身是没有优先级的,epoll唤醒和普通IO事件并没有做区分,唤醒的等级应该是最高的

这个MR就是做了个flag区分,让唤醒优先级更高一些

工作招聘

年底目前没有啥好工作推荐,我在公众微信号会单独发

互动环节

本周绝对不鸽好吧


上一期 下一期