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

考虑以下class继承体系

class Person {
public:
  Person(); // parameters omitted for simplicity
  virtual ~Person(); // see Item 7 for why this is virtual
  ...
private:
  std::string name;
  std::string address;
};
class Student: public Person {
public:
  Student(); // parameters again omitted
  virtual ~Student();
  ...
private:
  std::string schoolName;
  std::string schoolAddress;
};
bool validateStudent(Student s); // function taking a Student
// by value
Student plato; // Plato studied under Socrates
bool platoIsOK = validateStudent(plato); // call the function

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

bool validateStudent(const Student& s);

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

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

class Window {
public:
  ...
  std::string name() const; // return name of window
  virtual void display() const; // draw window and contents
};
class WindowWithScrollBars: public Window {
public:
  ...
  virtual void display() const;
};
void printNameAndDisplay(Window w) // incorrect! parameter
{ // may be sliced!
  std::cout << w.name();
  w.display();
}
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);

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

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

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

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

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

成员变量应声明为private

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

class AccessLevels {
public:
  ...
  int getReadOnly() const { return readOnly; }
  void setReadWrite(int value) { readWrite = value; }
  int getReadWrite() const { return readWrite; }
  void setWriteOnly(int value) { writeOnly = value; }
private:
  int noAccess; // no access to this int
  int readOnly; // read-only access to this int
  int readWrite; // read-write access to this int
  int writeOnly; // write-only access to this int
};

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

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

class WebBrowser {
public:
  ...
  void clearCache();
  void clearHistory();
  void removeCookies();
  ...
};

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

class WebBrowser {
public:
  ...
  void clearEverything(); // calls clearCache, clearHistory,
  // and removeCookies
  ...
};

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

void clearBrowser(WebBrowser& wb)
{
  wb.clearCache();
  wb.clearHistory();
  wb.removeCookies();
}

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

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

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

// header “webbrowser.h” — header for class WebBrowser itself
// as well as “core” WebBrowser-related functionality
namespace WebBrowserStuff {
class WebBrowser { ... };
  ... // “core” related functionality, e.g.
  // non-member functions almost
  // all clients need
}
// header “webbrowserbookmarks.h”
namespace WebBrowserStuff {
... // bookmark-related convenience
} // functions
// header “webbrowsercookies.h”
namespace WebBrowserStuff {
... // cookie-related convenience
} // functions
...

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

class Rational {
public:
  Rational(int numerator = , // ctor is deliberately not explicit;
  int denominator = ); // allows implicit int-to-Rational
  // conversions
  int numerator() const; // accessors for numerator and
  int denominator() const; // denominator — see Item22   const Rational operator*(const Rational& rhs) const;
private:
...
};

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

Rational oneEighth(, );
Rational oneHalf(, );
Rational result = oneHalf * oneEighth; // fine
result = result * oneEighth; // fine
result = oneHalf * ; // fine
result = * oneHalf; // error!

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

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

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

class Rational {
  ... // contains no operator*
};
const Rational operator*(const Rational& lhs, // now a non-member
const Rational& rhs) // function
{
  return Rational(lhs.numerator() * rhs.numerator(),
  lhs.denominator() * rhs.denominator());
}
Rational oneFourth(, );
Rational result;
result = oneFourth * ; // fine
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. git 使用(二)

    之前写过一篇git使用(一),那是入门篇,现在的(二)可以说是进阶篇吧,主要讲一些使用过程的注意事件及相关问题的解决办法. 一.push和fetch还需要输入用户名和密码? 解决办法:看看公玥是否添加 ...

  2. android-HttpClient上传信息(包括图片)到服务端

    需要下载apache公司下的HttpComponents项目下的HTTPCLIENT ----------地址为http://hc.apache.org/downloads.cgi 主要是用到了htt ...

  3. IP地址的定义和含义

    IP的定义 ip 是32位无符号整数,最小,最大分别是- 0.0.0.0 - 255.255.255.255 具体来说,由一个ip由 Net-ID+Host-ID 两部分组成,Net-ID 相同,那么 ...

  4. pyhton与json,Xml

    对简单数据类型的encoding 和 decoding: 使用简单的json.dumps方法对简单数据类型进行编码,例如: 1 2 3 4 5 6 import json   obj = [[1,2, ...

  5. MTK6577+Android环境变量

    1. 环境变量机器对应的路径 $project = hsimobile77_ics2 $platform=mt6577 $(PRODUCT_OUT)=\out\target\product\$proj ...

  6. Webservce、WCF、WebApi的区别

    Web Service It is based on SOAP and return data in XML form. It support only HTTP protocol. It is no ...

  7. 聚合函数字段注意.where和having的区别

    当使用聚合函数时,出现在select中的字段要么出现在聚合函数里,要么出现在group by 子句里.像下面这句是错误的: 1 SELECT  detno,AVG(sal),job FROM  emp ...

  8. Java编程思想 (1~10)

    [注:此博客旨在从<Java编程思想>这本书的目录结构上来检验自己的Java基础知识,只为笔记之用] 第一章 对象导论 1.万物皆对象2.程序就是对象的集合3.每个对象都是由其它对象所构成 ...

  9. UVa 10954 (Huffman 优先队列) Add All

    直接用一个优先队列去模拟Huffman树的建立过程. 每次取优先队列前两个数,然后累加其和,把这个和在放入到优先队列中去. #include <cstdio> #include <q ...

  10. HDU 1372 (搜索方向稍有改变) Knight Moves

    其实手写模拟一个队列也挺简单的,尤其是熟练以后. 尼玛,这题欺负我不懂国际象棋,后来百度了下,国际象棋里骑士的走法就是中国象棋里面的马 所以搜索就有八个方向 对了注意初始化标记数组的时候,不要把起点标 ...