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)) {
    ...
}

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

或许还有有人尝试用更多的方式来返回引用,但那一定会带来更多的开销。因此,正确的方式就是:在必须以值传递的方式返回对象时,直接以值传递的方式返回结果,而不要考虑返回其引用。
原文链接: https://www.cnblogs.com/xiaoxinxd/archive/2013/03/26/effective_cpp_20.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/82062

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月9日 下午8:24
下一篇 2023年2月9日 下午8:25

相关推荐