码迷,mamicode.com
首页 > 编程语言 > 详细

Effective Modern C++ 读书笔记 Item 1

时间:2016-08-10 06:23:52      阅读:298      评论:0      收藏:0      [点我收藏+]

标签:

最近发现了《Effective Modern C++》这本书,作者正是大名鼎鼎的Scott Meyers——《Effective C++》、《Effective STL》的作者。

而就在C++11逐渐普及,甚至是C++14的新特性也进入大家的视野的时候,《Effective Modern C++》一书应运而生。此书与其前辈一样,通过数十个条款来展开,只不过这次是集中于C++11和C++14的新特性。autodecltype、move、lambda表达式……这些强而有力的新特性背后到底隐藏着哪些细节和要点?……

阅读这本书的时候,感受到的豁然开朗的愉悦与初学C++时看Scott前几本著作时别无二致。遂尝试摘录一二,结合所想,做些记录,同时也试着检查一些自己的知识点有哪些欠缺,希望大家能多多指正。

 

注意:

蛤蛤蛤蛤蛤

      这样的方框里的片段完全来自于原书,而是我自己的理解。

 

Item 1 Understand template type deduction

Item 1 的标题大概是翻译成“理解模板类型推导”。模板类型推导是C++长期以来的特性。比如:

template<typename T>
void f(ParamType param);

f(expr);  // 调用 f

其中 ParamType 可以是和 有关的类型,只不过包含一些修饰,比如 const 或引用修饰符(reference qualifier)。如:

template<typename T>
void f(const T& param); // ParamType  const T&

对于这样的调用:

int x = 0;
f(x);

一个特化(Specialize)的函数就经由类型推导生成了,T 被推导(deduce)为 intParamType 则被推导为 const int&

上面这种过程是类型推导,而

template<> void f<int>(int);

就不算类型推导了——因为并没有进行“类型推导”,而是直接指定了——cppreference上将这叫做instantiate,实例化。编译器将特化有特定模板参数的函数模板。

 

在这种形式中,T 的推导不仅依赖于 expr 的类型,还和 ParamType 的形式(form)有关。对此书中给出三种情形:

 

情形1:ParamType 是指针(pointer)或引用(reference)类型,但不是universal引用(universal reference)

(此时书中并未详述什么叫universal引用,不过对此情形影响不大,因为universal引用首先就不是左值引用,即不是形如 int&、T&

在第一种情形下,类型推导有如下规则:

  1. expr 类型是引用,忽略引用部分;
  2. 这之后,把 expr 的类型对 ParamType 进行模式匹配来决定 T

上两条中,expr 指的就是函数的实参(argument),而 ParamType 是形参(parameter)的类型。书中例子为:

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

int x = 27;
const int cx = x;
const int& rx = x;

f(x);   // T -> int,       ParamType -> int&
f(cx);  // T -> const int, ParamType -> const int&
f(rx);  // T -> const int, ParamType -> const int&

expr,即上例的 x、cx、rx,去掉引用部分后为 int,const int,而 param 将要对这几种类型的变量建立引用,ParamType 就推导出了上述的结果。

其中很重要的一点:

当传递一个 const 对象到一个引用参数(parameter)时,调用者希望这个对象能保持 const 特性,即不变性。

模板类型推导遵从这一要点。故传递 const 对象到模板参数 T& 是安全的,不会丢失 const 属性。

并且以上规则对于右值引用也是成立的。

而将上例中的 ParamType 改为 const T& 时,上例三次调用全部将 ParamType 推导为 const int&T 则每次都为 int。因为 ParamType 的形式中带有了 const,匹配后 T 就不需要带有 const 了。

 

而对于 ParamType 是指针的情形,推导过程也是同样的。只是去除了“忽略引用部分”这一步,只是对指针类型进行模式匹配。

 

情形2:ParamType 是universal引用

模板函数的参数是universal引用的时候,比如“像是”右值(rvalue)引用,即 T&& 这样的类型,其中 T 的模板类型参数。

我想,所谓universal引用,可以先参考“引用叠加效果”表:

&  & -> &
&  && -> &
&&  & -> &
&&  && -> &&

我想这可能念做:&引用的&引用是&引用,&引用的&&引用是&引用,&&引用的&引用是&引用……

我的理解是,其中任一种引用的&&引用都是原型,所以叫做universal引用吧。由于是叠加在未确定的模板类型 T 上的,所以写法虽然一样,但并不是右值引用,因为右值引用是作用于明确类型上的。

参考资料 http://stackoverflow.com/questions/20364297/why-universal-references-have-the-same-syntax-as-rvalue-references

对于universal引用,类型推导规则为:

  1. 如果 expr 是左值(lvalue),则 TParamType 都推导为左值引用;
  2. 如果 expr 是右值(rvalue),则“普通”规则——第一种情形的规则被应用。

规则1可以对照引用叠加表,expr 的类型就是 -> 号右边的,如果它是左值即&,能通过universal引用变成这样状态的也只有左值&。即使 ParamType 被声明为和右值引用类似的形式,ParamType 本身也被推导为左值引用。

我认为这一推导正是由于待定的 ParamType 并不能表示一个右值引用类型,而只能作为一种“带有未知量 T”的类型运算表达式。比如 若是 int&&,则 T& 就是 int&

书中对于情形2的例子为:

template<typename T>
void f(T&& param); // param is now a universal reference
int x = 27; const int cx = x; const int& rx = x;
f(x); // x -> lvalue, T -> int&, ParamType -> int& f(cx); // cx -> lvalue, T -> const int&, ParamType -> const int& f(rx); // rx -> lvalue, T -> const int&, ParamType -> const int& f(27); // 27 -> rvalue, T -> int, ParamType -> int&&

 第4个调用即为退回情形1规则的情况。expr 是一个右值,则进行模式匹配后被绑定到 int&&,其中 Tint

 

情形3:ParamType 既非指针,也非引用

就像这样,按值传递/按拷贝传递(pass-by-value):

template<typename T>
void f(T param);

那么 param 总是对实参(argument)进行拷贝。此情形有规则:

  1. 如果 expr 类型是引用,忽略引用部分;
  2. 之后,如果 expr const,忽略 const。如果还是 volatile,也忽略这个。
int x = 27; 
const int cx = x; 
const int& rx = x; 
f(x); // T and ParamTypes -> int f(cx); // T and ParamTypes -> int f(rx); // T and ParamTypes -> int

因为 param 总是 expr 的拷贝,所以无论怎样都不会影响 expr,所以 expr constvolatile 这些特性,都和 param 无关了。

这也正符合上面所说的,调用者希望传入的对象原本具有的特性(如 const)不受影响,程序的实现要遵从这一希望。

原书在这里举了一个 const char * const 按拷贝传递(按值传递——相对于按引用传递)的例子,不细表。

 

数组作为实参

C/C++都有这样一个特性,那就是数组的退化(decay):

const char str[] = "hello";  // const char[6]
const char *p = str;         // 数组退化为指针

很明显 str p 的类型是不同的。而且对于C中的语法,是可以将函数的参数声明为数组的形式的,但是以下两者却是相同的:

void func(char str[]);
void func(char *str);

这是因为数组形式的形参(parameter),会被当作指针形式的形参处理。

因此对于按值传递的模板参数 T 来说,实参为数组 char[] 时,T 被推导为 char *。(可以认为数组的退化先发生。)

 

但模板参数为引用的时候,是能“真正”引用到传入的数组的(即不发生数组退化):

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

f(str);   // T -> const char[6], ParamType -> const char (&)[6]

一个例子,通过模板在编译期获取数组大小(代码中暂时无关的部分被去掉了):

template<typename T, std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) 
{ 
    return N;
}

 

函数作为实参

除了数组之外,函数也会退回为指针。但同时,同样能通过模板提供引用类型参数来避免退化:

void someFunc(int, double); // someFunc -> void(int, double)
template<typename T> void f1(T param);

template<typename T> void f2(T& param);
f1(someFunc); // ParamType -> void (*)(int, double) f2(someFunc); // ParamType -> void (&)(int, double)

数组和函数的退化都是针对其标识符。

 

此条款的注意点

  1. 类型推导中,引用类型的实参被视为非引用的。
  2. 对于universal引用形式的形参的类型推导,左值实参需要特殊处理。
  3. 按值传递的实参,其 const volatile 都会被无视。
  4. 类型推导中,使用数组或函数的标识符时,如不将其用于初始化引用,就会导致其退化为指针。

Effective Modern C++ 读书笔记 Item 1

标签:

原文地址:http://www.cnblogs.com/chelxom/p/5755125.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!