C++ 中文周刊 2024-11-16 第172期
公众号
点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了
qq群 点击进入 满了加这俩 729240657 866408334
欢迎投稿,推荐或自荐文章/软件/资源等,评论区留言
本期文章由 HNY 赞助 在此表示感谢
资讯
标准委员会动态/ide/编译器信息放在这里
编译器信息最新动态推荐关注hellogcc公众号 本周更新 2024-11-13 第280期
可以使用 -Wunsafe-buffer-usage
目前还在开发中
在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内部是由bthread驱动事件,可以理解为小的线程,类似boost fiber
bthread的调度是wait free的,设计了work steal,如果没有任务就去其他worker线程去偷
问题在于bthread本身是没有优先级的,epoll唤醒和普通IO事件并没有做区分,唤醒的等级应该是最高的
这个MR就是做了个flag区分,让唤醒优先级更高一些
工作招聘
年底目前没有啥好工作推荐,我在公众微信号会单独发
互动环节
本周绝对不鸽好吧