The Type System of Modern C++: 类型推演机制

C++ · horance · Created at · Last by horance Replied at · 1715 hits
3

C++ feels like a new language. -- Bjarne Stroustrup

增强了的「类型系统」是C++11最大的优化亮点之一,为此需要深入剖析「类型推演」的工作机理,并能灵活地运用auto, decltype,这是C++11最重要的基石。

templateauto

C++98早已具备类型推演的能力,用于模板的类型推演。

C++11中,autotemplate的类型推演能力基本类似,只存在唯一的差异:Braced Initialization,或称为Universal Initialization

非常量的左值引用

需要注意的是,推演auto &r2 = r, auto &cr2 = cr时,即使r(int&), cr(const int&)是引用变量,需要去除引用后再尝试类型推演,因为使用「引用变量」等价于使用其「引用对象」本身。

常量的左值引用

因为const T&const auto&已经具备了const的属性,当const的左值对象赋予它所发生的自动类型推演,其模板参数T,及其auto的类型无需推演为const属性。


指向非常量的指针

指针的推演能力与引用类似。

需要注意auto *p = &i; auto p = &i两种写法的不一样,一种是显式的指针类型,另外一种完全依赖于auto的类型推演能力。

指向常量的指针

与指向非常量的指针推演机制一致,在此不再冗述。


按值传递

Pass-By-Value,经过拷贝之后,两者之间已无任何瓜葛,为此const的处理机制有别于其他情况。



但存在两类特殊的,遗留的C-Style情况,为保证兼容性,存在特殊的类型推演机制。

遗留的C-style字符串

遗留的C-style函数

通用引用:Universal Reference

所谓Universal Reference,因为其能Can bind to anything,所以称为「通用引用」,具有如下方面的特点:

  • Can bind to lvalue or rvalue;
  • Can bind to const/non-const, volatile/non-volatile, or both;
  • So, it can bind to anything.

需要注意的是,Universal Reference并非「右值引用(Rvalue Reference)」,即使它们两者都有类似的T &&的修饰符。规则非常简单,Universal Reference具备两个最基本的特征:

  • T &&, auto&&: 必须具备的句法结构
  • type reduce:必须发生类型推演

可以简单归纳之,Universal Reference出现于如下两种常见:

template <typename T>
void f(T&& t);

auto&& r = i;

Universal Reference类型推演也存在特殊性:

  • Universal Reference持有左值时,发生Reference Collapsing机制。例如auto&& t = i,当auto推演为int&auto&& t推演为int& && t,而int& &&经过Reference Collapsing机制,被进一步规约为int&,与原来它持有左值刚好匹配。

  • Universal Reference持有右值时,推演规则较为直观,例如auto&& t = 10,当auto被推演为int,则auto&& t推演为int&& t,与原来它持有右值刚好匹配。



通用初始化:Braced Initialization

这是templateauto类型推演能力之间存在的唯一差别。

其他

关于C++类型系统的其他资料,请查阅如下文章:


「软件匠艺社区」旨在传播匠艺精神,通过分享好的「工作方式」和「习惯」以帮助程序员更加快乐高效地编程。
共收到 5 条回复
46
chenge · #1 ·

c++感觉好复杂。微信后端和阿里云这些是用什么开发的,c++么?

3
horance · #2 ·

#1楼 @chenge 我觉得C++的难度还行吧,关键它背后的「哲学思维」太适合我的「价值观」了(_)。至于C++的实用范围,我认为关乎「性能」和「抽象」平衡的C++,在性能关键的核心系统,依然是主流吧。

附件: C++设计的哲学思维

简单

Make Simple Tasks Simple.

  • Keep Simple Things Simple
  • Don't Make Complex Things Unnecessarily Complex
  • Don't Make Things Impossible

Constraint: Don't Cacrifice Performance.

平衡

Don’t Over Abstract

  • Abstraction
  • Performance

C++试图找到「抽象」和「性能」的平衡点,并将抉择的自由留给了程序员。

自由

  • No One Size Fits All
  • Multi-Paradigm

世界是多样性的,C++多范式的设计思维赋予了程序员极大的自由度和灵活性。

友好

  • More and More Expert Friendly

C++越来越变得更加友好,这种友好性对于专家感触将更加深刻。

3
horance · #3 ·

对于类型系统的推演能力,拿几个例子对比一下,就知道C++到底难不难了。

Java的类型系统

几乎没有类型推演机制,这个工作完全推给了程序员,所以你不得不写很多「样板代码」。

Map<String, Map<Integer, String>> map = new HashMap<String, Map<Integer, String>>();

这种情况,直至Java7/8引入了部分的自动推演能力才得到了「些许」改观,但程序员依然承受沉重的心智负担。

尤其,Java1.5引入的泛型设计,完全可以秒杀普通的Java程序员。以java.util.Comparator为例。

public interface Comparator<T> {
    public static <T, U> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor,
            Comparator<? super U> keyComparator)
    {
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
                                              keyExtractor.apply(c2));
    }

   ......
}

再来看C++的模板,功能相对于Java的兼容、适中方案,其功能强大,又能做到如此简单,当属不易了!

Scala的类型系统

Scala的「类型系统」与「隐式转换」几乎让90%的程序员面对Scala时望而止步了,复杂度不可言喻。

动态语言

没有静态的类型系统,虽然代码可以很简洁,但完全缺乏「类型安全」的保证,将所有错误都推迟到「运行时」。动态语言就是写着「爽歪歪」,运行时「哭歪歪」。

46
chenge · #4 ·

我比较喜欢clojure强调的不变性,学习中。你对clojure有何评价么?

c++我多年前折腾过,现在想换一下思路。

3
horance · #5 ·

#4楼 @chenge 我也得向楼主学习Clojure,我还是门外汉呢。不变性是「函数式」的思维,我也很推崇这样的「函数式」思维。「不变形」的确给设计带来诸多的益处,即使是「指令式」语言也会提倡「不变性」的良好「设计原则」。所以我很赞同楼主的观点。

OO makes code understandable by encapsulating moving parting, but FP makes code understandable by minimizing moving parts. -Michael Feathers

OO通过封装,抽象,依赖管理将可变性控制恰当,为此良好的OO设计必然是SOLID的,可组合的;FP的思路与众不同,与其控制其变化,不如让其不变。但是,这两种方法论殊途同归,都是为了做到更简单的、正交的、高内聚低耦合的设计。

对于ClojureScala,我选择了Scala,这完全是因为项目的原因。我相信「函数式」的高阶抽象,可以做到更好的「组合式设计」。但函数式也不是软件工程的唯一银弹,我更偏爱多范型的语言,例如C++,Scala,而不是所有问题只有一种思维,一种解法。至于选择函数式,面向对象,还是泛型,还得具体问题具体分析,关键看设计能否反映问题的本质。

需要 Sign In 后回复方可回复, 如果你还没有账号你可以 Sign Up 一个帐号。