您的位置:首页 > 编程语言 > C语言/C++

Effective c++学习笔记条款20:宁以 pass-by-reference-to-const替换pass-by-value

2011-10-05 19:48 561 查看
Prefer pass-by-reference-to-const to pass-by-value

        这个问题在在C++是非常常见的。传值和传引用巨大的差别在于你所使用的参数是其本身还是其副本。缺省情况下C++是以by value 传递的。其副本是对象本身的copy 构造函数自动调用的。有关对象自动调用的五个函数请看条款12.下面看一下例子吧    :
 
// refer_value.cpp : 定义控制台应用程序的入口点。
//2011/10/5 by wallwind at sunrise
#include "stdafx.h"
#include <iostream>
using namespace std;
class Base
{
public:
Base(){cout<<"Base()"<<endl;}
~Base(){cout<<"~Base()"<<endl;}
private:
string str1;
string str2;
};
class Derived:public Base
{
public:
Derived(){cout<<"Derived()"<<endl;}
~Derived(){cout<<"~Derived()"<<endl;}
bool retTrue()
{
return true;
}
private:
string str1;
string str2;
};
bool validateDerived(Derived d)
{
return d.retTrue();
}
int _tmain(int argc, _TCHAR* argv[])
{
Derived d;
bool bb=validateDerived(d);
system("pause");
return 0;
}

运行结果如图:


从结果我们可以看到 构造函数,析构函数都调用了,其实远不止这些,因为上述两个类都含有两个string,他们也相应的调用构造函数,就是说四次string构造动作,还有析构。
总计12次,也就是我们简单的一个调用,动用了这么多函数。很浪费资源。

下面我们用 pass-by-reference-to-const来。
修改代码为
 
bool retTrue() const
{
return true;
}

bool validateDerived(const Derived& d)
{
return d.retTrue();
}


运行的结果:


如图,程序只调用了构造函数。在这里注明书中说没有调用任何构造函数和析构函数,应该是调用了构造函数了吧。跪求高人解释

by-reference避免了对象切割,什么是对象切割,请看下边的例子。

// refer_value.cpp : 定义控制台应用程序的入口点。
//2011/10/5 by wallwind at sunrise
#include "stdafx.h"
#include <iostream>
using namespace std;
class Window
{
public :
Window(){};
void name()const;

virtual void display()const;
};
void Window::name()const
{
cout<<"window name"<<endl;
}
void Window::display()const
{
cout<<" window display()"<<endl;
}
class WindowWithScrollBars:public Window{

public:
WindowWithScrollBars(){};
virtual void display() const;
};
void WindowWithScrollBars::display() const
{
cout<<"WindowWithScrollBars display()"<<endl;
}
void pintNaandWind(Window w)
{
w.name();
w.display();
}
int _tmain(int argc, _TCHAR* argv[])
{
WindowWithScrollBars wwsb;
pintNaandWind(wwsb);
return 0;
}

运行的结果如图所示:


 
 

可以看出,我们想得到WindowWithScrollBars display() ,但是我们得到的是上述的。它调用 是window对象,而不是子类的。因为他是by value,WindowWithScrollBars的所有特化信息都会被切除,这就是对象切割。那么解决办法就是
pass-by-reference-to-const,
修改代码如下:
void pintNaandWind(const Window& w)

{

w.name();

w.display();
}


运行结果得到:


 

从中我们看到了通过pass-by-reference-to-const,得到了我们想要的结果。

解释一下:

(1)所谓的slicing问题,也就是如果你把子类的对象赋值给父类的对象,如果用reference和指针,当然是可以的,而且这也是virtual的实现必需品。但是,如果用pass-by-value,就会出现,传进去的总是父类的对象,在传递对象的时候出现了切割问题。

(2)如果窥视c++编译器的底层,你会发现,references往往以指针实现出来,因此pass-by-reference通常意味着这真正传递的是指针。因此如果你有个对象属于内置类型(如int),pass by reference或pass by reference to const时,选择pass by value并非没有道理。这个忠告也适用于STL的迭代器和函数对象,因为习惯上它们都被设计为pass by value。迭代器和函数对象的实践者有责任看看他们是否高效且不受切割问题的影响。

(3)内置类型都相当的小。因此有人认为,所有小类型的types都是pass by value的合格候选人,甚至他们是用户自定义的class亦然。这是个不可靠的推论。对象小并不就意味其copy构造函数不昂贵。许多对象,包括STL容器,内含的东西只比一个指针多一些,但复制这种东西对象却需要承担“复制那些指针的每一样东西”。那将非常昂贵。

即使小型对象拥有并不昂贵的copy构造函数,还是可能有效率上的争议。某些编译器对待“内置类型”和“用户自定义类型”的态度截然不同,总是两者有相同的底层表述。举个例子,某些编译器拒绝把只由一个double组成的对象放进缓存器内,却很乐意在一个正规基础上对光秃秃的double那么做。当这种事发生,你更应该以by reference方式传递此等对象,因为编译器当然会将指针(references的实现体)放进缓存器内,绝无问题。

小型的用户自定义类型并不必然成为pass by value优良候选人的另一个理由是,作为一个用户自定义类型,其大小容易有所改变。甚至当你改用另一个c++编译器都有可能改变type的大小。举个例子,某些标准程序库实现版本中的string类型比其他版本大七倍!

请记住:
▲ 尽量以pass-by-reference-to-const替换pass-by-value.前者通常比较高效,并可避免切割问题(slicing

problem).

▲ 以上规则并不适合与内置类型,以及STL的迭代器和函数对象.对它们而言,pass-by-value往往比较适合.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息