zoukankan      html  css  js  c++  java
  • Effective C++函数参数传递方式

     

    C++函数参数传递方式(Effective C++之20, 21)


    1. 引用传递与值传递的选择

    2. 返回值的传递

    1. 引用传递与值传递的选择

    熟悉C++的人都知道,C++中函数参数的默认传递方式是值传递(pass-by-value),这种传递方式的好处是在函数内部使用的是实参的一个副本,在函数内部对其操作不会影响实参的值。但是我们也知道,对象的拷贝是会有时间和空间消耗的,而且如果对象所占空间很大的话,以值来传递参数很可能相当费时而极大程序的限制了程序的性能。C++提供了一种高效的对象传递方式:引用传递。

    自定义类型使用值传递带来的问题:

    第一,传递对象的效率低。传递对象会调用对象的构造函数,造成时间和空间的浪费。特别如果对象所占的空间很大时,带来的负面效果尤为明显。

    第二,会引起“对象切割”问题。当子类以被视为父类的对象进行值传递时,传递之后,那些属于子类的特征化信息会丢失掉,这绝对不是想要的结果。可以看下面的例子:

    复制代码
    class Window {
    public:
        ...
        std::string name() const;
        virtual void display() const;
    };
    
    class WindowWithScrollBars : public Window {
        ...
        virtual void display() const;
    };
    复制代码

    以值传递WindowWithScrollBars的对象,而参数的类型是Window类型:

    复制代码
    void printNameAndDisplay(Windown w)
    {
        std::cout << w.name();
        w.display();
    }
    
    WindowWithScrollBars wwsb;
    printNameAndDisplay(wwsb);
    复制代码

    在上面的代码中,wwsb以值进行传递,传递之后对象变为Window类型,所以调用w.display()调用的函数是Window::display(),而不是期望的WindowWithScrollBars::display()。当使用const引用传递时会解决这一问题。

    第三,即使对象的规模较小,也不就使用值传递的方式。因为某些编译器对待自定义类型和内置类型的方式不同。例如:某些编译器会把double类型的变量放进寄存器内,却不会对只由一个double类型的成员构造的对象这么做,但是编译器却会把指针放进寄存器(reference是通过指针实现的)。

    综上

    • 《Effective C++》中对选择参数传递方式的总结:除了内置类型、STL的迭代器和函数对象(后两者通常认为拥有高效的拷贝效率)可以被设计为值传递之外,其他任何类型都应该使用const引用传参(pass-by-reference-to-const)。

    2. 返回值的传递

    引用传递确实能够带来一些效率、性能上的好处,但是也不能滥用。如果一心追求在任何时候都将使用引用传递,将会带来一个致使的错误,那就是传递的引用指向了并不存在的对象。这一节介绍一个在什么情况下不适合返回引用。(示例来自Effective C++条款21)

    一个有理数的类,声明了一个friend函数计算两个有理数的乘积:

    复制代码
    class Rational {
    public:
        Rational(int numerator = 0, int denominator = 1);
        ...
    private:
        int n, d;
    friend const Rational operator* (const Rational& lhs, const Rational& rhs);
    };
    复制代码

     这个乘积函数以值传递来返回对象,那么显然它会有一些开销。那么可不可以考虑用引用传递来返回结果呢?要用引用传递,就不能返回局部变量,因为函数返回时局部变量会销毁,只能采用栈或堆中的对象。如下面的实现方式:

    const Rational& operator* (const Rational& lhs, const Rational& rhs)
    {
        Rational* result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
        return *result;
    }

     现在,确实实现了引用的方式返回参数,但是还是有一个“构造函数调用”的开销。而且,还有一个更大的问题:new出来的对象谁负责delete。假如要求调用者每次都会适当的释放内存。但是下面的代码:

    Rational w, x, y, z;
    w = x * y * z;

     在这个过程中,调用了两次operator*,但是没办法拿到中间那次乘法返回的引用,也就不能正确进行delete,绝对导致了内存泄漏。但如果禁止这种嵌套调用,那么显然这个乘法的实现是很糟糕的。

    还有一种方式来返回引用,让返回的引用指向一个被定义于函数内部的static Rational对象:

    const Rational& operator* (const Rational& lhs, const Rational& rhs)
    {
        static Rational result;
        ...
        return result;
    }

     众所周知,用static对象都会带来多线程安全的问题。但是这种实现方式还会带来另外一个更大的漏洞。考虑如下代码:

    bool operator==(const Rational& lhs, const Rational& rhs);
    Rational a, b, c, d;
    if ((a * b) == (c * d)) {
        ...
    }

     这种调用方式,会造成a*b永远和c*d相等,if条件永远为true。因为a*b和c*d返回的引用指向了同一个static对象。

    或许还有有人尝试用更多的方式来返回引用,但那一定会带来更多的开销。因此,正确的方式就是:在必须以值传递的方式返回对象时,直接以值传递的方式返回结果,而不要考虑返回其引用。

     
     
    分类: C++
    标签: C++Effective C++引用
  • 相关阅读:
    鸟哥的linux私房菜学习-(八)Linux 文件与目录管理
    我的作品
    聊聊软件测试面试的一些事
    如何做一名专业的软件测试工程师
    测试Leader应该做哪些事
    软件测试工程师的岗位职责
    一个完整的性能测试流程
    做接口测试需要哪些技能
    软件质量保障体系建设
    性能测试常见瓶颈分析及调优方法
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2983479.html
Copyright © 2011-2022 走看看