c++ template-3

第 7 章 按值传递还是按引用传递

        从一开始,C++就提供了按值传递(call-by-value)和按引用传递(call-by-reference)两种参数传递方式,但是具体该怎么选择,有时并不容易确定:通常对复杂类型用按引用传递的成本更低,但是也更复杂。C++11 又引入了移动语义(move semantics),也就是说又多了一种按引用传递的方式:
1. X const &(const 左值引用)
参数引用了被传递的对象,并且参数不能被更改。
2. X &(非 const 左值引用)
参数引用了被传递的对象,但是参数可以被更改。
3. X &&(右值引用)
参数通过移动语义引用了被传递的对象,并且参数值可以被更改或者被“窃取”。仅仅对已知的具体类型,决定参数的方式就已经很复杂了。在参数类型未知的模板中,就更难选择合适的传递方式了。
不过在 1.6.1 节中,我们曾经建议在函数模板中应该优先使用按值传递,除非遇到以下情况: 对象不允许被 copy。

  • 参数被用于返回数据。
  • 参数以及其所有属性需要被模板转发到别的地方。
  • 可以获得明显的性能提升。

        本章将讨论模板中传递参数的几种方式,并将证明为何应该优先使用按值传递,也列举了不该使用按值传递的情况。同时讨论了在处理字符串常量和裸指针时遇到的问题。在阅读本章的过程中,最好先够熟悉下附录 B 中和数值分类有关的一些术语(lvalue,rvalue,prvalue,xvalue)。

7.1 按值传递

        当按值传递参数时,原则上所有的参数都会被拷贝。因此每一个参数都会是被传递实参的一份拷贝。对于 class 的对象,参数会通过 class 的拷贝构造函数来做初始化。调用拷贝构造函数的成本可能很高。

        但是有多种方法可以避免按值传递的高昂成本:事实上编译器可以通过移动语义(move semantics)来优化掉对象的拷贝,这样即使是对复杂类型的拷贝,其成本也不会很高。
        比如下面这个简单的按值传递参数的函数模板: 

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>

template<typename T>
void printV(T arg) {


}

int main() {
  std::string returnString();
  std::string s = "hi";
  printV(s); //copy constructor
  printV(std::string("hi")); //copying usually optimized away (if not,move constructor)
  printV(returnString()); // copying usually optimized away (if not, moveconstructor)
  printV(std::move(s)); // move constructor
  return 0;
}

        在第一次调用中,被传递的参数是左值(lvalue),因此拷贝构造函数会被调用。

        但是在第二和第三次调用中,被传递的参数是纯右值(prvalue,pure right value,临时对象或者某个函数的返回值,参见附录 B),此时编译器会优化参数传递,使得拷贝构造函数不会被调用。从 C++17 开始,C++标准要求这一优化方案必须被实现。在 C++17 之前,如果编译器没有优化掉这一类拷贝,它至少应该先尝试使用移动语义,这通常也会使拷贝成本变得比较低廉。

        在最后一次调用中,被传递参数是 xvalue(一个使用了 std::move()的已经存在的非const 对象),这会通过告知编译器我们不在需要 s 的值来强制调用移动构造函数(move constructor)。

        综上所述,在调用 printV()(参数是按值传递的)的时候,只有在被传递的参数是lvalue(对象在函数调用之前创建,并且通常在之后还会被用到,而且没有对其使用std::move())时, 调用成本才会比较高。不幸的是,这唯一的情况也是最常见的情况,因为我们几乎总是先创建一个对象,然后在将其传递给其它函数

按值传递会导致类型退化(decay)

        关于按值传递,还有一个必须被讲到的特性:当按值传递参数时,参数类型会退化(decay)。也就是说,裸数组会退化成指针,const 和 volatile 等限制符会被删除(就像用一个值去初始化一个用 auto

        

#include <utility>
#include <string>
#include <iostream>
#include <type_traits>

template<typename T>
void printV(T arg) {


}

int main() {
  std::string const c = "hi";
  printV(c); // c decays so that arg has type std::string
  printV("hi"); //decays to pointer so that arg has type char const*int arr[4];
  int arr[4];
  printV(arr); // decays to pointer so that arg has type int *
  return 0;
}

        当传递字符串常量“hi”的时候,其类型 char const[3]退化成 char const *,这也就是模板参数 T 被推断出来的类型。此时模板会被实例化成: 

void printV (char const* arg)
{ …
}

        这一行为继承自 C 语言,既有优点也有缺点。通常它会简化对被传递字符串常量的处理,但是缺点是在 printV()内部无法区分被传递的是一个对象的指针还是一个存储一组对象的数组。在 7.4 节将专门讨论如何应对字符串常量和裸数组的问题。 

 7.2 按引用传递

        现在来讨论按引用传递。按引用传递不会拷贝对象(因为形参将引用被传递的实参)。而且,按引用传递时参数类型也不会退化(decay)。不过,并不是在所有情况下都能使用按引用传递,即使在能使用的地方,有

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值