从reddit/hackernews/lobsters/meetingcpp摘抄一些c++动态。
每周更新
欢迎投稿,推荐或自荐文章/软件/资源等,请提交 issue
一个游戏公司使用c++的经验分享
可读性
- 使用snake_case
- 不用简写
- 注释
例子
int32 new_held_milliseconds= update_context->get_timestamp_milliseconds() - m_start_hold_timestamp_milliseconds; set_output_property_value_and_accumulate( &m_current_held_milliseconds, new_held_milliseconds, &change_flags, FLAG(_input_event_listener_change_flag_current_held_milliseconds)); bool should_trigger_hold_event= m_total_hold_milliseconds > NONE && m_current_held_milliseconds > m_total_hold_milliseconds && !m_flags.test(_flag_hold_event_triggered); if (should_trigger_hold_event) { // Raise a flag to emit the hold event during event processing, and another // to prevent emitting more events until the hold is released m_flags.set(_flag_hold_event_desired, true); m_flags.set(_flag_hold_event_triggered, true); }
格式统一
- 英语拼写用美式英语 color colour的区别
- 用i++而不是++i
- *和&靠近变量而不是靠近类型
~emmm啥品味啊这个~
还是格式
- 宏大写
- 赋值不留空格
void c_screen_manager::render() { bool ui_rendering_enabled= true; #ifdef UI_DEBUG_ENABLED const c_ui_debug_globals *debug_globals= ui::get_debug_globals(); if (debug_globals != nullptr && debug_globals->render.disabled) { ui_rendering_enabled= false; } #endif // UI_DEBUG_ENABLED if (ui_rendering_enabled) { // ... } }
~emmmm啥品味啊这~
充分利用名字符号
- c_ 表示class, e_ 表示枚举 m_ 表示成员变量, k_表示常量
- 函数名,get_blank(),try_get_blank要有如名字一样稳定的表现,让使用者放心
维护性
- 声明直接初始化
- 函数一个return
- 有assert保证状态正确性
- 不用默认数组类型用其他实现的容器
~emmm最后一条不明白缘由~
资源管理中心话
- 内部内存分配器,不直接调用malloc
- 不用stl
int main() {
constexpr int array[] = {1, 2, 3};
assert(2[array] == array[2]);
assert(*(array+1) == array[1]);
}
这学c的没有不知道的吧。没啥用反正。注意这个坑吧
介绍boost::hana的使用这里有个cheatsheet 元编程,编译器计算,类型和值混起来保存,类似场景,boost::hana是一个好用的现代的库
介绍继承模式
作者遇到的场景
struct heart {
void pump_blood(){ std::cout << "baboom, baboom\n"; }
bool consume_sugar(){
if (sugar_level < 0)
return false;
--sugar_level;
return true;
}
int sugar_level = 100;
};
template <class Base>
struct legs : Base {
void move_legs(){
this->pump_blood();
std::cout << "legs are moving...\n";
}
};
template <class Base>
struct body : Base {
// hopefully Base has all the methods we need
void move(float dx, float dy){
if (not this->consume_sugar())
return;
this->move_legs();
position[0] += dx;
position[1] += dy;
}
float position[2] = {0, 0};
};
using final_body = body<legs<heart>>;
using final_body = body<legs<heart>>;
这种是单向的依赖,如何抽出一套组合模版?
这里直接放代码
include <utility>
#include <type_traits>
template <class... Ts>
struct typelist{};
struct mixin_tag{};
template <class T>
inline constexpr bool is_mixin = std::is_base_of_v<mixin_tag, T>;
template <std::size_t Idx, class Mxn>
struct mixin_base {
Mxn& root(){ return *static_cast<Mxn*>(this); }
const Mxn& root() const { return *static_cast<const Mxn*>(this); }
};
template <class Seq, class Mxn, class Core, template <class...> class... Frags>
struct mixin_impl;
template <std::size_t... Idx, class Mxn, class Core, template <class...> class... Frags>
struct mixin_impl<std::integer_sequence<std::size_t, Idx...>, Mxn, Core, Frags...>
: Core, Frags<mixin_base<Idx, Mxn>>...
{
using frags_list = typelist<Core, Frags<mixin_base<Idx, Mxn>>...>;
};
template <class Core, template <class...> class... Frags>
struct mixin :
mixin_impl
<
std::make_index_sequence<sizeof...(Frags)>,
mixin<Core, Frags...>,
Core,
Frags...
>,
private mixin_tag
{
};
template <class F>
void invoke_all_one(auto& mxn, auto&& fn){
if constexpr ( requires { fn(static_cast<F&>(mxn)); } )
fn(static_cast<F&>(mxn));
}
template <class... Frags>
void invoke_all_impl(auto&& mxn, auto&& fn, typelist<Frags...>){
(invoke_all_one<Frags>(mxn, fn), ...);
}
template <class Mxn, class Fn>
requires is_mixin<std::decay_t<Mxn>>
void invoke_all(Mxn&& mxn, Fn&& fn){
using list = typename std::decay_t<Mxn>::frags_list;
invoke_all_impl(mxn, fn, list{});
}
以及使用案例
enum class state {
happy,
angry,
sad,
asleep
};
template <state State>
struct tag{};
template <class R>
struct brain : R {
template <class Tag>
void set_state(Tag){
constexpr auto impl = [] (auto& frag)
requires requires { frag.change_state(Tag{}); }
{ frag.change_state(Tag{}); };
invoke_all(this->root(), impl);
}
enum state state;
};
template <class R>
struct mouth : R {
void change_state(tag<state::angry>){
std::cout << "SKREEEEEEEEEEEEEEEE\n";
}
};
template <class R>
struct claws : R {
void change_state(tag<state::angry>){
std::cout << "protracting claws...\n";
}
};
template <class R>
struct tail : R {
void change_state(tag<state::angry>){
std::cout << "unfolding tail...\n";
}
};
struct feet{};
using creature = mixin<feet, tail, claws, mouth, brain>;
int main(){
creature c;
c.set_state( tag<state::angry>{} );
}
/*
unfolding tail...
protracting claws...
SKREEEEEEEEEEEEEEEE
*/
作者文章写的不错,了解LLVM的可以看一看
介绍浮点数怎么比较,一般来说都是精度比较,给定一个精度,误差范围内决定大小,知乎这里也有个讨论值得一看
另外,这篇文章,英文介绍要更详细一些
这里有一系列讨论,英文
这里多说一下ULP这种方法,wiki看这里
代码
/* See
https://randomascii.wordpress.com/2012/01/11/tricks-with-the-floating-point-format/
for the potential portability problems with the union and bit-fields below.
*/
#include <stdint.h> // For int32_t, etc.
union Float_t
{
Float_t(float num = 0.0f) : f(num) {}
// Portable extraction of components.
bool Negative() const { return i < 0; }
int32_t RawMantissa() const { return i & ((1 << 23) - 1); }
int32_t RawExponent() const { return (i >> 23) & 0xFF; }
int32_t i;
float f;
#ifdef _DEBUG
struct
{ // Bitfields for exploration. Do not use in production code.
uint32_t mantissa : 23;
uint32_t exponent : 8;
uint32_t sign : 1;
} parts;
#endif
};
bool AlmostEqualUlps(float A, float B, int maxUlpsDiff)
{
Float_t uA(A);
Float_t uB(B);
// Different signs means they do not match.
if (uA.Negative() != uB.Negative())
{
// Check for equality to make sure +0==-0
if (A == B)
return true;
return false;
}
// Find the difference in ULPs.
int ulpsDiff = abs(uA.i - uB.i);
if (ulpsDiff <= maxUlpsDiff)
return true;
return false;
}
constexpr和vector可以结合使用
#include <vector>
#include <numeric>
#include <algorithm>
struct Point {
float x, y;
constexpr Point& operator+=(const Point& a) noexcept {
x += a.x;
y += a.y;
return *this;
}
};
constexpr bool testVector(int n) {
std::vector<Point*> vec(n);
for (auto& pt : vec) {
pt = new Point;
pt->x = 0.0f;
pt->y = 1.0f;
}
Point sumPt { 0.0f, 0.0f};
for (auto &pt : vec)
sumPt += *pt;
for (auto& pt : vec)
delete pt;
return static_cast<int>(sumPt.y) == n;
}
int main() {
static_assert(testVector(10));
}
缺点,不能这么用
constexpr std::vector vec = compute();
分配内存不是constexpr的,所以后面要allocator来支持
用array绕过还是可以的
讲异步抽象的对象生命周期问题
讲std::osyncstream的
这段代码
int main()
{
std::vector<std::jthread> threads;
for (int i = 1; i <= 10; ++i)
{
threads.push_back(
std::jthread([](const int id)
{
std::cout << "I am thread [" << id << "]" << '\n';
}, i));
}
}
打印可能是乱的,但这段代码不是
int main()
{
std::vector<std::jthread> threads;
auto worker = [](std::string text) { std::cout << text; };
auto names = { "Alpha", "Beta", "Gamma", "Delta", "Epsilon" };
using namespace std::string_literals;
for (auto const& name : names)
threads.push_back(std::jthread(worker, "Hello, "s + name + "!\n"));
}
首先std::cout是线程安全的(除非设置sync_with_stdio(false)
) 问题出在哪里?operator<<
怎么保证operator<<
的原子性呢?用std::osyncstream
我怎么感觉是为了填坑补充的东西。
怎么用我就不介绍了。我觉得这套东西,别用,也别搞多线程printf的需求,这种需求一般log库都会去实现,加个队列来搞
讲auto的没啥说的