今天讲一个 Idiom 加一些 Tricks。 本次内容紧紧围绕着 The Rule of the Big Five,即 destructor copy constructor copy assignment constructor move constructor move assignment constructor 要讲的 Idiom 主要是针对后三个,Tricks 则主要是针对后两个,属于是一些额外的优化技巧。 先从 The Big 3 说起,就是前三个,由此引出 Idiom 存在的必要性。一个小例子: class my_string { public: // default ctor my_string(const char* str = "") : size_ { std::strlen(str) }… Continue Reading T230723 copy-and-swap Idiom and More Tricks

本次谈逻辑分派。 三种最基本的逻辑关系为相似、承接和因果关系。 逻辑分派主要指的是因果关系,因果关系里面又包含条件关系。因果关系是一种非常特殊且重要的关系,表示两个功能块之间具备极强的依赖性。功能 B 只有依赖于功能 A 才能存在,彼此不能分开。 像简单功能模块使用的 if-else,constexpr if, Parametric Polymorphism 当中使用的 Partial Specialization/Tag dispatching/SFINAE/Concepts,Ad hoc Polymorphism 当中使用的 overload resolution,还有 Subtyping Polymorphism 当中使用的 virtual function,都属于逻辑分派。 结构从本质上来说,是一种逻辑。程序结构本质是程序的逻辑体现,混乱的逻辑代表混乱的结构,清晰的逻辑代表清晰的结构,结构越简洁、越清晰,程序越具有稳定性,扩展维护起来也比较轻松。 早期的这些逻辑分派,大多依赖于 type,反而是最朴素的 if-else 依赖于 value。依赖 type 往往需要定义很多的类型,甚至很简单的功能也要定义一个类,随着代码模块增多,类会越来越多,这叫类膨胀问题。而依赖于 value 是一种更偏向于 Ad hoc Polymorphism 的方式,Functional Programming 就属于此类,此时功能的最小单元通常使用函数。 Dispatch table 就属于一种轻量级的逻辑分派方式。 一个简单的示例: int main(){ std::map<const char… Continue Reading T230713 Dispatch table

今天谈谈 inline constexpr。 上次讲过 static constexpr,它用于 function scope/class scope ,此时 constexpr 会隐式 inline (class scope),static 则表示存储时期为 static。组合起来使用,一是能够直接在类中初始化通过 default member initializer 定义静态成员,二是所有对象能够共享数据,三是可以强保证发生于编译期,四是能够强保证 Lambdas 隐式捕获此数据。同时,也能够解决 SIOF。 上次也讲过 static inline,但是没有谈及 inline constexpr。 讨论之前,大家再回忆一个提过的准则:constexpr 在 file scope 下会隐式 internal linkage。 所以在 file scope 下不会写 static constexpr,这里的 static 是冗余的。但是单独只写一个 constexpr 修饰数据,默认的 internal linkage 会导致在多个 TUs… Continue Reading T230707 inline constexpr

提到 C++,许多人的印象绝对不会少了一个词:复杂。 的确,2000 页左右的标准,甚至一些特性单独就能成一本书,怎么能不复杂? 但也别忘了它是一门接近 40 年的编程语言。 系统大了,问题本身就很复杂,很多时候并不存在完美的解决方案。很多问题的一面是另一个系统的另一面,解决这个问题必然会带来另一个问题。所以许多时候都要分清主次,把主要模板的问题潜移默化地排到其他次要模块中去,解决当下的问题。 问题一直都没有消失,只是在这个大系统中滚来滚去。要想真正解决问题,就得不断寻求增量,增加新特性,很多旧的问题就这样通过增量解决了。随之而来的又是新的问题,循环往复~ 新特性使用起来更方便,很多时候就是因为从更高维度解决了问题。一些新语言也只是暂时把一些问题转移到了次要方面,时间一长照样暴露无遗。也正是因为总是追求完美,而问题本身又极具复杂性,且缺少许多前置组件,又要兼容旧的,所以标准才加得超慢。 不同定制实现就是在矛盾中自己决定主次,想一招吃遍天下不可能。默认定制适用范围越广,特性用起来就越简单。但一旦换个场景,增加定制是否也这么简单,就不好说了。 Bjarne 在 Herb 的某次演讲最后说: there’s a huge chunk of the C++ community that love complexity. They want every little detail of the implementation being obvious, so that they know exactly how many nanoseconds it might cost. Sometimes I’m on… Continue Reading T230705 Complex C++

这次谈成员初始化。 C++存在 3 种方式初始化成员,分别为: ctor member initializer list ctor body default member initializer (in-class initializer) 关于 ctor member initializer list 和 ctor body 这2种方式,记得 Effective C++ 有条款单独讨论过,但那书太早了,只包含C++98/03,default member initializer 是C++11才有的方式。 在C++98,只有 static const int 才能够直接在类内直接初始化,它可以保证初始化是在编译期完成的。 // C++98 struct S { static const int x = 42; // OK const int… Continue Reading T230621 Member Initialization

前几天群里聊到了初始化,所以这次选择该主题。 这个主题也是一个基本内容,乃大厦之基,联系太过广泛,所以也是可深可浅。 局部值、全局值、成员值、静态值、内联值、线程值……每一个单独拿出来可能还好理解,但一组合起来就比较复杂了。有些内容,如果不懂操作系统、编译原理和汇编,还难以真正理解其来龙去脉。尤其是静态值,一个 static 本身就有十几种不同的意思,再和 inline, constexpr, constinit……一组合,可谓是复杂到了极点。 这次先来统一下变量初始化的相关术语,更多内容后面再来说。 C++ 初始化包含 default initialization, value initialization, direct initialization, copy initialization, list initialization, aggregate initialization, reference initialization 这么几种。 声明一个变量,不做任何初始化(或是类中的成员没有初始化),就会采用 default initialization。 比如 type t; new type; 这里 type 可以是 primitive types 和 class-types,primitive types 的默认初始化是保持未初始化,class-types 会调用 default ctor。 要让 primitive types 和… Continue Reading T230612 Variable initialization

有一段时间没写 TGS 了,继续更新。 今天谈一个基本概念—— Polymorphism,这是大多数初学者最初接触的概念,要说它简单,那你可能想简单了。它是一个可以串起 C++98-26 发展历程的一个概念,像之前的五星「重载决议」内容,都仅仅只是这条逻辑链下的某一个小支点,它至少链接了 50+ 条概念,非常复杂。 Polymorphism 最基本的目标就是,同一功能,多种实现。 首先引出的就是 Type System,因为功能需要针对不同的类型,来定制多种实现。类型一致性必须定义明确,才能在此基础上构建功能,主要存在两种定义,一种是 Nominal,一种是 Structural。 Nominal 又叫 name-based type system,这种方式以名称来定义类型一致性,打个比方,夏侯婴、夏侯惇、夏侯渊在这种类型系统中就被认为是同一种类型,具有类型一致性,那么同一种功能就可以作用到所有这种类型上去。Structural 又叫 property-based type system,这种方式以属性来定义类型一致性,某些人都说粤语,这是一个共同属性,那么就被视为是同一种类型,只要附带这个属性,就具备调用功能的条件。 采用 Nominal 的主要是 Subtype Polymorphism 和 Ad hoc Polymorphism,而采用 Structural的方式的主要是 Parametric Polymorphism。 Subtype Polymorphism 是 Object-oriented programming 中才有的,C++ 通过Inheritance+virtual functions来支持这种方式,根据同一继承体系的名称来定义类型一致性,只要在高层级名称中提供默认行为,其他低层级名称就可以使用这种默认行为,也可以重新改写行为,由此支持同一功能,多种实现这一目标。它的问题在于,一些第三方库可能无法派生,或是有些类型不满足is-a关系,强制其进入同一 type system,是一种侵入式的行为。 因此 C++ 产生了一种技术来解决这种缺点,就是… Continue Reading T230531 C++ Polymorphism

今天说一下额外 () 产生不同意义的情况。 多数情况下,额外的()是不影响语义的,但在以下 5 种情况,有无 () 则意义不同,此时 () 就有了特殊的作用。 禁止 ADL。 在「洞悉 C++ 函数重载决议」提到过,使用额外的 () 可以防止 ADL。 namespace N { struct S { }; void f(S); } void g() { N::S s; f(s); // OK: calls N::f (f)(s); // error: N::f not considered; parentheses // prevent argument-dependent lookup }… Continue Reading T230428 Extra Parentheses, Different Meaning

今天说两个关于编译期的小技巧。 看如下例子: struct S { int val; constexpr int size() const { return val * (val + 1) / 2; } }; constexpr auto bad(S s) { // doesn't compile return std::array<int, s.size()>{}; } int main() { constexpr S s{42}; auto my_array = bad(s); } 这里 bad() 的参数 s 就称为… Continue Reading T230420 Unconditional compile-time expression