以pass-by-reference-to-const 替换pass-by-value

考虑以下class继承体系

  1. class Person {
  2. public:
  3.   Person(); // parameters omitted for simplicity
  4.   virtual ~Person(); // see Item 7 for why this is virtual
  5.   ...
  6. private:
  7.   std::string name;
  8.   std::string address;
  9. };
  10. class Student: public Person {
  11. public:
  12.   Student(); // parameters again omitted
  13.   virtual ~Student();
  14.   ...
  15. private:
  16.   std::string schoolName;
  17.   std::string schoolAddress;
  18. };
  19. bool validateStudent(Student s); // function taking a Student
  20. // by value
  21. Student plato; // Plato studied under Socrates
  22. bool platoIsOK = validateStudent(plato); // call the function

本次以by value方式传递一个Student对象会导致一次Student 构造函数、一次Person 构造函数、四次string构造函数,共六次构造函数。当销毁时,同样学要六次的析构函数,可见其效率是如此的低,而使用pass-by-reference-to-const可以有效地回避原本需要的构造,析构带来的性能损耗。

  1. bool validateStudent(const Student& s);

这种方式来传递参数效率高很多,因为没有新的对象被创建,其中的const的作用是保证传入参数在执行时不被修改,而使用by value传递参数要达到这种效果是通过对实参做一个副本,然后在副本上做修改,效率上的优劣是显而易见的。

通过by reference 传递还可以避免对象被切割的问题,当一个派生类对象以By value 传递时被视为了一个基类对象,派生类特有的特性被舍弃掉了

  1. class Window {
  2. public:
  3.   ...
  4.   std::string name() const; // return name of window
  5.   virtual void display() const; // draw window and contents
  6. };
  7. class WindowWithScrollBars: public Window {
  8. public:
  9.   ...
  10.   virtual void display() const;
  11. };
  12. void printNameAndDisplay(Window w) // incorrect! parameter
  13. { // may be sliced!
  14.   std::cout << w.name();
  15.   w.display();
  16. }
  1. WindowWithScrollBars wwsb;
  2. printNameAndDisplay(wwsb);

以上代码的本意是在printNameAndDisplay中传递一个wwsb的对象,调用派生类版本的display函数,但由于是值传递,代码执行时实际上运行的是基类的display,解决方法时改为引用传递避免对象被切割:

  1. void printNameAndDisplay(const Window& w) // fine, parameter won’t
  2. { // be sliced
  3.   std::cout << w.name();
  4.   w.display();
  5. }

pass-by-reference确实在很多时候扮演提高效率,减少错误的角色,但它也不是万能的,以下就举个有理数乘机的例子来说明

  1. class Rational {
  2. public:
  3.   Rational(int numerator = , // see Item 24 for why this
  4.   int denominator = ); // ctor isn’t declared explicit
  5.   ...
  6. private:
  7.   int n, d; // numerator and denominator
  8.   friend
  9.   const Rational // see Item 3 for why the
  10.   operator*(const Rational& lhs, // return type is const
  11.   const Rational& rhs);
  12. };

这个例子中并没有使用引用类型返回是有道理的,引用只是个名称,它代表这一个已经存在的对象,如果我们在上面代码中把返回类型Rational改为Rational&,就相当于我在两个有理数乘积之前已经存在了一个两数相乘的结果,我返回的只是这个结果变量的一个别名,这听起来很傻比,但如果有些情况下返回引用,它的意思就是这样。所以在选择使用引用传递和值传递时要仔细考虑我们到底需要表达的是什么意思。

成员变量应声明为private

如果成员变量都声明为public,会造成每个人都可以读写它,而实际应用中我们并不希望对用户开放所有权限,所以将变量声明为private可以控制每个变量的读写权限,这样也提高了类的封装性

  1. class AccessLevels {
  2. public:
  3.   ...
  4.   int getReadOnly() const { return readOnly; }
  5.   void setReadWrite(int value) { readWrite = value; }
  6.   int getReadWrite() const { return readWrite; }
  7.   void setWriteOnly(int value) { writeOnly = value; }
  8. private:
  9.   int noAccess; // no access to this int
  10.   int readOnly; // read-only access to this int
  11.   int readWrite; // read-write access to this int
  12.   int writeOnly; // write-only access to this int
  13. };

使用非成员函数替代成员函数

假设有个class来表示浏览器,它其中需要实现清除缓存,历史记录,cookies.

  1. class WebBrowser {
  2. public:
  3.   ...
  4.   void clearCache();
  5.   void clearHistory();
  6.   void removeCookies();
  7.   ...
  8. };

用户需要整个执行所有动作,因此浏览器也提供这样的功能

  1. class WebBrowser {
  2. public:
  3.   ...
  4.   void clearEverything(); // calls clearCache, clearHistory,
  5.   // and removeCookies
  6.   ...
  7. };

以上是成员函数的实现方案,非成员函数则可以写成这样。

  1. void clearBrowser(WebBrowser& wb)
  2. {
  3.   wb.clearCache();
  4.   wb.clearHistory();
  5.   wb.removeCookies();
  6. }

直觉告诉我们成员函数的实现更好些,但可惜的是这个直觉是错误的,面向对象要求数据尽可能的被封装,即越少的人可以看到内部数据,封装性越好,所以显然在这里我们选择非成员函数。

像WebBrowser这样的类所提供的功能肯定有很多,我们让整个核心机能组成一个类,而对于另外一些功能就没有必要放在同一个文件中,没有理由cookies管理与书签管理产生编译相依的关系

,分离他们的做法就是将各个辅助功能各自声明于不同的头文件,像stl那样<vector>,<algorithm>,<memory>只是属于同一个命名空间,我们可以根据需要来增加头文件,没有必要把整个stl增加进来。

  1. // header “webbrowser.h” — header for class WebBrowser itself
  2. // as well as “core” WebBrowser-related functionality
  3. namespace WebBrowserStuff {
  4. class WebBrowser { ... };
  5.   ... // “core” related functionality, e.g.
  6.   // non-member functions almost
  7.   // all clients need
  8. }
  9. // header “webbrowserbookmarks.h”
  10. namespace WebBrowserStuff {
  11. ... // bookmark-related convenience
  12. } // functions
  13. // header “webbrowsercookies.h”
  14. namespace WebBrowserStuff {
  15. ... // cookie-related convenience
  16. } // functions
  17. ...

非成员函数除了提高封装性外,当遇到参数需要隐式的类型转换,也需要使用非成员函数

  1. class Rational {
  2. public:
  3.   Rational(int numerator = , // ctor is deliberately not explicit;
  4.   int denominator = ); // allows implicit int-to-Rational
  5.   // conversions
  6.   int numerator() const; // accessors for numerator and
  7.   int denominator() const; // denominator — see Item22
  8.  
  9.   const Rational operator*(const Rational& rhs) const;
  10. private:
  11. ...
  12. };

以上是个关于有理数操作类,处理两个有理数乘积,看上去设计很合理,但遇到下面代码时就出现了问题。

  1. Rational oneEighth(, );
  2. Rational oneHalf(, );
  3. Rational result = oneHalf * oneEighth; // fine
  4. result = result * oneEighth; // fine
  5. result = oneHalf * ; // fine
  6. result = * oneHalf; // error!

在执行result = oneHalf * 2;这条语句时,onehalf是一个有opeator*函数的对象,"2"则是作为参数传递,编译器直接理解为

  1. const Rational temp(); // create a temporary
  2. // Rational object from 2
  3. result = oneHalf * temp; // same as oneHalf.operator*(temp);

即把‘2’隐式转换成了Rational对象,而当执行result = 2 * oneHalf;时出现错误是"2"不能隐式转换引起的,隐式转换产生的条件是参数必须列于参数列表中,这条规则导致了第二条语句执行错误而第一条语句正确。找到原因之后,我们需要做的是把参与乘法的两个数都作为参数放到参数列表中。

  1. class Rational {
  2.   ... // contains no operator*
  3. };
  4. const Rational operator*(const Rational& lhs, // now a non-member
  5. const Rational& rhs) // function
  6. {
  7.   return Rational(lhs.numerator() * rhs.numerator(),
  8.   lhs.denominator() * rhs.denominator());
  9. }
  10. Rational oneFourth(, );
  11. Rational result;
  12. result = oneFourth * ; // fine
  13. result = * oneFourth; // hooray, it works!

effective c++:引用传递与值传递,成员函数与非成员函数的更多相关文章

  1. 理解Java中的引用传递和值传递

    关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天学习 ...

  2. (转载)PHP数组传递是值传递而非引用传递

    (转载)http://www.fengfly.com/plus/view-212127-1.html 在调用函数时通过将PHP数组作为实参赋给形参,在函数中修改,并不会影响到数组本身. 说明此过程中的 ...

  3. Java Object 引用传递和值传递

    Java Object 引用传递和值传递 @author ixenos Java没有引用传递: 除了在将参数传递给方法(或函数)的时候是"值传递",传递对象引用的副本,在任何用&q ...

  4. (转载)理解Java中的引用传递和值传递

      关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天 ...

  5. java到底是引用传递还是值传递?

    今天我们来讲讲一个在学习中容易误解的问题,面试中也偶尔问到,java方法调用时到底是值传递还是引用传递? 首先,请大家来做一个判断题,下面的3个问题是否描述正确 1. java基本数据类型传递是值传递 ...

  6. Java中的引用传递和值传递

    Java中的引用传递和值传递 关于Java的引用传递和值传递,在听了老师讲解后,还是没有弄清楚是怎么一回事,于是查了资料,所以在这里与大家分享,有不对的地方,欢迎大家留言. java中是没有指针的,j ...

  7. Java中没有引用传递只有值传递(在函数中)

    ◆传参的问题 引用类型(在函数调用中)的传参问题,是一个相当扯的问题.有些书上说是传值,有些书上说是传引用.搞得Java程序员都快成神经分裂了.所以,我们最后来谈一下“引用类型参数传递”的问题. 如下 ...

  8. Java千百问_05面向对象(011)_引用传递和值传递有什么差别

    点击进入_很多其它_Java千百问 1.什么是值传递 值传递,是将内存空间中某个存储单元中存放的值,传送给还有一个存储单元.(java中的存储单元并不是物理内存的地址,但具有相关性) 比如: //定义 ...

  9. java引用传递和值传递

    关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天学习 ...

随机推荐

  1. python抓取百度热词

    #baidu_hotword.py #get baidu hotword in news.baidu.com import urllib2 import os import re def getHtm ...

  2. POJ 1903 & ZOJ 2469 & UVA 1326 Jurassic Remains (部分枚举)

    题意:给定n个只有大写字母组成的字符串,选取尽可能多的字符串,使得这些字符串中每个字母的个数都是偶数.n<=24 思路:直接枚举每个字符串的选或不选,复杂度是O(2^n).其实还有更简便的方法. ...

  3. 《Linux/Unix系统编程手册》读书笔记7 (/proc文件的简介和运用)

    <Linux/Unix系统编程手册>读书笔记 目录 第11章 这章主要讲了关于Linux和UNIX的系统资源的限制. 关于限制都存在一个最小值,这些最小值为<limits.h> ...

  4. Zend13.0 +XAMPP3.2.2 调试配置

    Zend 调试PHP有3种方式: (1)PHP CLI APPLICATION (2)PHP Web Application (3)PHP UnitTest (1).(2)两种方式配置相似,下图是配置 ...

  5. Eclipse插件安装与使用 —— Properties Editor

    一.下载     首先当然是下载插件啦! 下载地址:http://sourceforge.jp/projects/propedit/downloads/40156/jp.gr.java_conf.us ...

  6. Java 基础-反射

    反射-Reflect 测试用到的代码 1.接口 Person.java public interface Person { Boolean isMale = true; void say(); voi ...

  7. Charles是Mac的Fiddler抓包工具

    windows下面我们经常使用 Fiddler 抓包工具进行代理等一系列操作.然而,在 Mac 下的 Fiddler 勉强能运行,但是其挫的都不想说它了.今天看到朋友推荐这款 Charles Mac下 ...

  8. Git基础(三)

    本章 就开始和大家一起学习第三块内容:远程仓储的使用操作.要参与任何一个 Git 项目的协作,必须要了解该如何管理远程仓库.远程仓库是指托管在网络上的项目仓库,可能会有好多个,其中有些你只能读,另外有 ...

  9. C# 类的访问修改符

    C#共有五种修饰符:public.private.protected.internal.protected internal. ◆public:公有,对所有类可见,不受任何限制 ◆protected: ...

  10. jQuery 动画 _animate() 方法

    一.jQuery animate() 方法用于创建自定义动画. 必需的 params 参数定义形成动画的 CSS 属性. 可选的 speed 参数规定效果的时长.它可以取以下值:"slow& ...