main() 里面,
```
auto m2 = ::max(s1, s2, s3); //run-time ERROR
```
这句会进入第 3 个 function template
```
// maximum of three values of any type (call-by-reference)
template<typename T>
T const& max (T const& a, T const& b, T const& c)
{
return max (max(a,b), c); // error if max(a,b) uses call-by-value
}
```
这句又进入了第 2 个 function template
```
// maximum of two C-strings (call-by-value)
char const* max (char const* a, char const* b)
{
return std::strcmp(b,a) < 0 ? a : b;
}
```
此时,由于返回类型是 char const*,一个指针 variable,不论 a/b 哪个更大,都会返回一个 variable,类型是 char const*,值是 a/b 中 strcmp 较大的那个指向的地址。这个 variable 就是所谓的 temporary variable。
类似于
```
T a = foo();
```
foo() evaluate 完之后,所有 foo() 中的变量 life cycle 都结束了,那 assignment 要拿谁做等号右边的 variable ?这种情况就会产生一个 temporary variable 用来临时存放返回值,等 assignment 结束后,temporary variable 的 life cycle 也结束了。当然,实际代码中 temporary variable 可能被 RVO 优化掉,更或者被 C++17 的 copy elision 处理掉。这里不展开了。
第 2 个 function template return 后,回到第 3 个 function template,此时,等价于
```
return max(temporary_variable, c);
```
这里会再进一次第 2 个 function template,返回后等价于
```
return temporary_variable_2;
```
然而,第 3 个 function template 返回的类型是 T const&,也就是返回了 temporary variable 的引用,一直传递到了 main 里面,而这个 temporary variable 的 life cycle 也就到第 3 个 function template 结束而已。对 temporary variable 的使用超过的它的 life cycle,是一种 run time error。
此时会不会 crash 就是 UB 了,一般编译器不会做类似 variable life cycle 一结束就清除它的内存之类激进的事情,所以 temporary variable 的内存地址里“可能”暂时还会是它原本的内容( UB ),将这些字节解释回变量的内容也“可能”得到原来变量的值( UB again )。并且
```
auto m2 = ::max(s1, s2, s3);
```
这里,auto 会得到 decay 的类型,去掉了引用,因此只要这个 temporary variable “曾经”所在的内存能撑过这句,就能得到原本的变量值。
而
```
auto m1 = ::max(7, 42, 68); // OK
```
没问题的原因是它从头到尾就不会进第 2 个 function template,始终是引用飞来飞去,引用的就是 7/42/68 这三个 integer literal 产生的 temporary variable,life cycle 是到该语句结束,然后被 auto 得到 decay 的类型后 copy 一份。不属于 UB。
港真,好好写人能读懂的代码,不要乱飞这些乱七八糟的类型更重要。
1. 为什么实际工程中不用 g++ 命令?
其实也用,只不过是隐含在 makefile 里,执行 makefile 时候会自动调用。很多参考书给的用例,因为只有一个 cpp 文件,特意编写 makefile 来组织显得过于繁冗,因而直接一句 g++ 命令生成目标文件方便快捷。而实际工程中动辄几百个 cpp/header/so/a 文件,文件之间还存在依赖关系,这种情况下仍然手工一句一句 g++ 来编译实在过于复杂而且低效。
2. makefile 本质就是描述工程中的依赖关系和编译参数。执行时会自动根据依赖关系确定编译顺序,按序编译。makefile 是 unix-like 系统下的解决方案,Windows 下一般使用 MSVC 的 sln 工程文件。本质都是一样的东西。CMake 是一个跨平台的解决方案,执行时根据选择的目标平台不同将 CMakeList.txt “翻译”成 makefile 或者 vcxproj。
3. 自己搞个几十个 cpp 的小项目,跑一跑,在实践中摸索熟悉吧。相关的书籍有 《程序员的自我修养》、《 GNU Make 项目管理》。还有个文章《跟我一起写 Makefile 》。