以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. c# 计算1-100之间的所有质数(素数)的和

    求1-100之间的所有质数(素数)的和: 所以先必须知道什么是质数? 一个数是不是素数   “素数”是指除了能被1和自身整除外,不能被任何其它整数整除的自然数. 然后知道有哪些是质数: 记忆的方法: ...

  2. PHP代码优化技巧大盘点

    PHP优化的目的是花最少的代价换来最快的运行速度与最容易维护的代码.本文给大家提供全面的优化技巧. 1.echo比print快. 2.使用echo的多重参数代替字符串连接. 3.在执行for循环之前确 ...

  3. spring mvc 中文乱码 post与get的方法解决

    spring mvc表单提交中文参数乱码问题 今天测试spring mvc  ,中文乱码,在web.xml中加上 <filter> <filter-name>encodingF ...

  4. appium安装

    appium 这个移动端的自动化测试框架.是神器啊.selenium系列的工具.webdirver是一个使用很广泛的自动化测试框架. 至于API 测试,等,使用代码做单元测试就好了,各种框架很多,只要 ...

  5. Android内存管理(2)HUNTING YOUR LEAKS: MEMORY MANAGEMENT IN ANDROID PART 2

    from: http://www.raizlabs.com/dev/2014/04/hunting-your-leaks-memory-management-in-android-part-2-of- ...

  6. C++关键字 inline

    在C&C++中 一.inline关键字用来定义一个类的内联函数,引入它的主要原因是用它替代C中表达式形式的宏定义. 表达式形式的宏定义一例: #define ExpressionName(Va ...

  7. STL笔记(3) copy()之绝版应用

    STL笔记(3) copy()之绝版应用 我选用了一个稍稍复杂一点的例子,它的大致功能是:从标准输入设备(一般是键盘)读入一些整型数据,然后对它们进行排序,最终将结果输出到标准输出设备(一般是显示器屏 ...

  8. hdu3270Arranging Your Team(dfs)

    http://acm.hdu.edu.cn/showproblem.php?pid=3720 #include <iostream> #include<cstdio> #inc ...

  9. R语言缺失值信息处理

    mean(!is.na(mat))可以计算数据完整度(没有缺失值的) mean(!is.na(mat))>0.9,90%完整可以使用 # 缺失值的位置研究as.vector(attributes ...

  10. 多线程-NSOperation中使用ASIHttpRequest注意事项

    最近做的iPhone项目中有一如下功能: app在用户许可后将本地Photos的照片上传到服务器,期间用户可以做其他任何操作,等上传成功后弹出一个toast通知用户. 原先的代码结构是: 获取照片的操 ...