文章目录
设计与声明
条款20. 用引用代替值传递
在不改变函数传递对象的前提下,函数参数尽量使用引用,总所周知的功能主要有以下两点
- 能提高传参效率,避免多次拷贝与析构。
- 避免对象切割。
以代码为例。
class Window{
public:
...
std::string name() const; //返回窗口名称
virtual void display() const; //显示窗口和其内容
};
class WindowWithScrollBars:public Window{
public:
...
virtual void display()const;
};
当发生如下调用时:
void printNameAndDisplay(Window w)
{
std::cout << w.name();
w.display();
}
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb); //照成对象切割,只留下基类的特性
而对于内置对象(例如int)还有STL中的迭代器与函数对象使用值传递比较合适。
条款21.返回对象时,别返回引用(reference)
- 由于返回的函数内部local stack对象,在引用钱被销毁。
- 返回reference指向一个heap-allocated对象,会存在内存泄漏的问题。
- 返回reference指向一个local static时可能需要多个这样的对象
条款22.将成员变量声明为private
以及对成员变量的可控性都要求将变量声明为private。C++三大特性的封装为以后替换
不同的实现方式提供了可能。
protected并不比public更加具有封装性。
条款23.用non-member,non-friend替换member函数
对于不直接涉及class私有成员的函数尽量使用non-member与,non-friend函数替代。可以导致较低的编译相依度与增加class的可延伸性。以一个用于表示网页浏览器的class为例。在其众多的函数中有一些用来清除下载高速缓存区、清除访问过的URLs的历史记录,以及移除系统中的所有cookies:
class WebBrower{
public:
...
void clearCache();
void clearHistory();
void removeCookies();
...
void clearEverything(); //调用上面三个清除函数
};
也可以使用non-member函数替换上面clearEverying函数
void clearBrowser(WebBrowser& wb)
{
wb.clearCache();
wb.clearHistory();
wb.removeCookies();
}
由于non-member函数并不能增加“能够访问class内之private成分”,提供了较大的封装性。
一种较为常见的做法为将clearBrowser成为一个non-member函数位于WebBrowser所在的一个namespace(命名空间)内:
namespace WebBrowserStuff{
class WebBrowser{...};
void clearBrowser(WebBrowser& wb);
...
}
对于一个WebBrowser这样的class可以拥有大量便利函数,将各个功能分类声明与一个头文件中
//头文件WebBrowser.h——这个头文件针对class WebBrowser自身
//及WebBrowser核心机能
namespace WebBrowserStufff{
class WebBrowser{...};
... //核心机能。所有客户都需要
//non-member函数
}
//头文件“webbrowserbookmarks.h”
namespace WebBrowserStuff{
... //与书签相关的便利函数
}
//头文件“webbrowsercookies.h”
namespace WebBrowserStuff{
... //与cookie相关的便利函数
}
这种方式类似于C++标准库,可以轻松扩展命名空间中的便利函数。
条款24.若所有参数需要类型转换,请为此采用non-member函数。
对于隐式类型转换的class,建立数值型类时,计算时常常需要隐式转换。
class Rational{
public:
Rational(int numberator = 0,int denominator = 1); //允许隐式转换
int numberator() const;
int denominator() const;
const Ration operator* (const Rational& rhs) const;
private:
...
};
普通的类之间的计算没问题,但是混合计算时会出现问题
Rational oneEighth(1,8);
Rational oneHalf(1,2);
Rational result = oneHalf * oneEighth; //OK
result = result * oneEighth; //OK
result = oneHalf * 2; //2通过隐式转换成 Rational temp(2),OK
result = 2 * oneHalf ; //error
为了支持混合运算可以将operator* 成为一个non-member函数。
class Rational{
... //不包含operator*
};
const Rational operator*(cosnt Rational& lhs,const Rational& rhs)
{
return Ration(lhs.numerator()*rhs.numerator(),lhs.denominator()*rhs.denominator());
}
Rational oneFourth(1,4);
Rational result ;
result = 2 * oneFourth ; //ok
- 如果要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必定是个non-member。
条款25.写出一个不抛出异常的swap函数
缺省下的swap动作由标准程序库swap算法完成
namespace std{
template<typename T>
void swap(T& a,T& b)
{
T temp(a);
a = b;
b = temp;
}
}
缺省的swap通过重复copy构造函数和copy assignment操作符完成,效率极低。
对于那种pimpl(pointer to implementation)"以指针指向一个对象,内含真正数据"的那种类型。使用缺省的swap会很大的降低效率。
举个例子
class WidgetImpl{
public:
...
private:
int a,b,c;
std::vector<doublle> v;
...
};
class Widget{
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs) //赋值Widget时,令它赋值其WidgetImpl对象
{
...
*pImpl = * (rhs.pImpl);
...
}
private:
WidgetImpl* pImpl; //指针,所指对象内含Widget数据
};
可以如下改写,与STL容器具有一致性
class Widget{
public:
...
void swap(Widget& other) //Widget成员函数胡
{
using std::swap;
swap(pImpl,other.pImpl);
...
}
};
namespace std{
templat<> //模板类全特化,std::swap针对“T是Widget”的特化版本
void swap<Widget>(Widget& a,Widget& b)
{
a.swap(b); //置换Widgets,调用其swap成员函数
}
};
另外一种情况,Widget与WidgetImpl都是class templates而非classes,在Widget内(以及WidgetImpl内)放个swap成员函数和上述一样。但是注意特化std::swap时,由于模板函数不支持偏特化(partially specialize)所以不能如之前那样
namespace std{
template<typename T>
void swap< Widget<T>>(Widget<T>&a,Widget<T>&b) //错误!模板函数不支持偏特化
{ a.swap(b);}
}
当然可以如下操作,为打算偏特化的函数模板添加一个重载版本。
namespace std{
template<typename T>
void swap(Widget<T>& a,Widget<T>& b) //(注意swap之后没跟<...>)
{a.swap(b);}
}
但是这样子违反了STL的标准。
一种较为简单的方式是声明一个non-member swap,让它调用member swap,将所有相关机能都被置于命名空间WidgetStuff内。
namespace WidgetStuff{
... //模板化的WidgetImpl等等
template<typename T> //同前,内含swap成员函数
class Widget{...};
...
template<typename T> //non-member swap函数
void swap(Widget<T>&a,Widget<T>&b) //这里并不属于std命名空间。
{ a.swap(b); }
}
如何确保每次能够最佳调用,在专属版本不存在的情况下调用std内的一般化版本
template<ytpename T>
void doSomething(T& obj1,T& obj2)
{
using std::swap; //令std::swap在此函数内可用
...
swap(obj1,obj2); //为T型对象调用最佳swap版本
...
}
C++名称查找法则(name lookup rules)确保将找到global作用域或T所在之命名空间内的任何T专属的swap。如果T是Widget并位于命名空间WidgetStuff内,编译器会使用“实参取决之查找规则”(argument-dependent lookup)找出WidgetStuf内的swap。如果没有T专属的swap存在,编译器就使用std内的swap。
且不需要为调用添加额外的修饰符
std::swap(obj1,obj2); //这是错误的swap调用方式