zoukankan      html  css  js  c++  java
  • 第7章 按值传递或按引用传递:7.1 按值传递

    Chapter 7: By Value or by Reference?

    第7章 按值传递或按引用传递?

    Since the beginning, C++ has provided call-by-value and call-by-reference, and it is not always easy to decide which one to choose: Usually calling by reference is cheaper for nontrivial objects but more complicated. C++11 added move semantics to the mix, which means that we now have different ways to pass by reference:

    从一开始,C++就提供了按值和按引用两种传递参数的方式。但具体该怎么选,有时并不容易确定:通常对于非平凡的对象(译注:此时可理解为复杂类型的对象)按引用传递成本更低,但也更复杂。C++11新增移动语义,意味着现在我们多了一种按引用传递的方式:

      1. X const& (const左值引用):

        The parameter refers to the passed object, without the ability to modify it.

        参数引用了被传递的对象,但没办法修改该对象。

      2. X& (非const左值引用):

        The parameter refers to the passed object, with the ability to modify it.

        参数引用了被传递的对象,而且该对象可以被修改。

      3. X&& (右值引用):

        The parameter refers to the passed object, with move semantics, meaning that you can modify or “steal” the value.

        参数引用了被传递的对象,通过移动语义,意味着你可以修改或“窃取”其值。

    Deciding how to declare parameters with known concrete types is complicated enough. In templates, types are not known, and therefore it becomes even harder to decide which passing mechanism is appropriate.

    决定如何用己知的具体类型声明参数己经很复杂了(即传值或传引用)。在模板中,类型是未知的,因此,如何决定一种合适的传参机制将变得更加困难。

    Nevertheless, in Section 1.6.1 on page 20 we did recommend passing parameters in function templates by value unless there are good reasons, such as the following:

    不过,在第20页的1.6.1节中我们曾经建议在函数模板中按值传递参数,除非有充足的理由,例如:

      • Copying is not possible.

        对象不允许拷贝的

      • Parameters are used to return data.

        参数被用于返回数据

      • Templates just forward the parameters to somewhere else by keeping all the properties of the original arguments.

        原始参数及其所有属性需要被模板转发到别的地方。

      • There are significant performance improvements.

        性能可以获得明显提升的。

    This chapter discusses the different approaches to declare parameters in templates, motivating the general recommendation to pass by value, and providing arguments for the reasons not to do so. It also discusses the tricky problems you run into when dealing with string literals and other raw arrays.

    本章将讨论模板中几种传递参数的方式,阐述了通常建议使用按值传递的动机,也列举了一些不该使用按值传递的情况。同时还讨论了在处理字符串字面量和其他原生数组时可能会遇到的棘手问题。

    When reading this chapter, it is helpful to be familiar with the terminology of value categories (lvalue, rvalue, prvalue, xvalue, etc.), which is explained in Appendix B.

    在阅读本章的过程中,最好先熟悉一下附录B中与“值类型”有关的专门术语(lvalue、rvalue、prvalue和xvalue等)。

    7.1 Passing by Value

    7.1 按值传递

    When passing arguments by value, each argument must in principle be copied. Thus, each parameter becomes a copy of the passed argument. For classes, the object created as a copy generally is initialized by the copy constructor.

    当按值传递参数时,原则上所有的参数都会被拷贝。因此,每一个参数都是传入参数的副本。对于类而言,创建的对象副本通常是通过拷贝构造函数来初始化的。

    Calling a copy constructor can become expensive. However, there are various way to avoid expensive copying even when passing parameters by value: In fact, compilers might optimize away copy operations copying objects and can become cheap even for complex objects by using move semantics.

    调用拷贝构造函数的成本可能很高。但是,有多种方法可以避免昂贵的拷贝,即使是通过按值传递参数也可避免。实际上,在拷贝对象时编译器可能会将拷贝操作优化掉。对于复杂对象也可以使用移动语义来使得成本变低。

    For example, let’s look at a simple function template implemented so that the argument is passed by value:

    例如,下面这个简单的函数模板实现,其参数是通过按值传递的:

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

    When calling this function template for an integer, the resulting code is

    当用int类型调用函数模板时,实例化结果的代码为:

    void printV (int arg) {
        …
    }

    Parameter arg becomes a copy of any passed argument, whether it is an object or a literal or a value returned by a function.

    参数arg变成是传入的任意实参的副本,不管实参是一个对象、字面量还是函数的返回值。

    If we define a std::string and call our function template for it:

    如果我们定义一个std::string对象,并调用这个函数模板:

    std::string s = "hi";
    printV(s);

    the template parameter T is instantiated as std::string so that we get

    模板参数T被实例化为std::string,我们将得到的代码是:

    void printV (std::string arg)
    {
        …
    }

    Again, when passing the string, arg becomes a copy of s. This time the copy is created by the copy constructor of the string class, which is a potentially expensive operation, because in principle this copy operation creates a full or “deep” copy so that the copy internally allocates its own memory to hold the value.

    同样,当传入字符串时arg变成了s的一份副本。这一次副本是通过std::string的构造函数创建的,这可能会是一个成本很高的操作。因为,这个拷贝操作会进行完全或“深”拷贝,在对象内部会开辟内存空间来存储字储串的值。

    However, the potential copy constructor is not always called. Consider the following:

    但这种潜在的拷贝操作并不是总会发生。考虑下面的情况:

    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, move constructor)
    printV(std::move(s)); // move constructor

    In the first call we pass an lvalue, which means that the copy constructor is used.

    在第1个调用中,传入的是一个左值。因此会调用拷贝构造函数。

    However, in the second and third calls, when directly calling the function template for prvalues (temporary objects created on the fly or returned by another function; see Appendix B), compilers usually optimize passing the argument so that no copying constructor is called at all. Note that since C++17, this optimization is required. Before C++17, a compiler that doesn’t optimize the copying away, must at least have to try to use move semantics, which usually makes copying cheap. In the last call, when passing an xvalue (an existing nonconstant object with std::move()), we force to call the move constructor by signaling that we no longer need the value of s.

    但是,在第2个和第3个调用中,当直接向函数模板中传入prvalue类型(动态创建的临时对象、另一个函数的返回值。参见附录B)的参数时,编译器通常会对参数进行优化,使得拷贝构造函数根本不会被调用。注意,从C++17开始,标准要求这一优化方案必须被实现。但在C++17之前,编译器如果没有进行拷贝优化,它至少应该尝试使用移动语义,这通常也会使得拷贝的成本变成更低(译注:即在C++17之前会先进行拷贝优化,如果不行,再尝试使用移动语义)。在最后一个调用中,当传入一个xvalue类型(对一个己经存在的非const对象使用std::move()进行转换)的参数时,这将告知编译器我们不再需要s的值并强制调用移动构造函数。

    Thus, calling an implementation of printV() that declares the parameter to be passed by value usually is only expensive if we pass an lvalue (an object we created before and typically still use afterwards, as we didn’t use std::move() to pass it). Unfortunately, this is a pretty common case. One reason is that it is pretty common to create objects early to pass them later (after some modifications) to other functions.

    因此,在调用按值传递参数的printV()函数时,只有在被传递的参数是一个左值类型时(一个在函数调用之前创建,并且经常在之后还会被用到,而且没有对其使用std::move()的对象),调用成本才会比较高。不幸的是,这是一种很常见的情况。因为我们几乎总是先创建一个对象,然后(经常一些修改后),再将其传递给其它函数。

    Passing by Value Decays

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

    There is another property of passing by value we have to mention: When passing arguments to a parameter by value, the type decays. This means that raw arrays get converted to pointers and that qualifiers such as const and volatile are removed (just like using the value as initializer for an object declared with auto):

    关于按值传递,还有另一个性质必须被讲到:当按值传递参数时,其类型会发生退化(decay)。这意味着原生数组会退化为指针类型,const和volatile限制符会被删除(就像用一个值去初始化用auto声明的对象一样)

    template<typename T>
    void printV (T arg) {
        …
    }
    
    std::string const c = "hi";
    
    printV(c); // c 退化,因此arg为std::string类型
    printV("hi"); //退化为指针类型,因此arg为char const*类型
    int arr[4];
    printV(arr); //退化为指针类型,因此arg为char const*类型

    Thus, when passing the string literal "hi", its type char const[3] decays to char const* so that this is the deduced type of T. Thus, the template is instantiated as follows:

    所以,当传递字符串字面量”hi”时,它的类型会从char const[3]退化为char const*,这也是模板参数T被推导出来的类型。模板被实例化为:

    void printV (char const* arg)
    {
        …
    }

    This behavior is derived from C and has its benefits and drawbacks. Often it simplifies the handling of passed string literals, but the drawback is that inside printV() we can’t distinguish between passing a pointer to a single element and passing a raw array. For this reason, we will discuss how to deal with string literals and other raw arrays in Section 7.4 on page 115.

    这种行为继承自C语言,它既有优点也有缺点。这通常会简化对字符串字面量的处理,但其缺点是在printV()内部我们无法区分被传递的参数是一个对象的指针还是原生数组。为此,我们将在第115页的7.4节中讨论如何处理字符串字面量和其他原生数组的问题。

  • 相关阅读:
    软件版本具体代表什么意思
    面向接口的编程
    MySQL mysqldump用法
    常见问答解答
    Perl 语言笔记
    JAVA 基础知识
    面向接口编程的基本原则
    判断文件存在与否【Linux】
    Gtk Label设置字体颜色
    解压缩路径设置【Linux】
  • 原文地址:https://www.cnblogs.com/5iedu/p/12783715.html
Copyright © 2011-2022 走看看