条款20:宁已pass-by-reference-to-const替换pass-by-value
1.尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题。
class Window {
public:
std::string name() const; // 返回窗口名称
virtual void display() const; // 显示窗口和其内容
};
class WindowWithScrollBars : public Window {
public:
virtual void display() const;
};
void printfNameAndDisplay(Window w) { // 不正确!参数可能被切割
std::cout << w.name();
w.display();
}
当你调用上述接口并交给他一个WindowWithScrollBars对象
WindowWithScrollBars wwsb;
printfNameAndDisplay(wwsb);
参数会被构造成为一个Window对象,他是pass-by-value,因此在printfNameAndDisplay调用display总是Window::display()
解决切割问题的办法,就是以pass-by-reference-to-const的方式传递w
void printfNameAndDisplay(const Window& w) { // 很好,参数不会被切割
std::cout << w.name();
w.display();
}
2.以上规则并不重点内容试用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比较适当。
以上.
条款21:必须返回对象时,别妄想返回其reference
绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象。
当你必须在“返回一个reference和返回一个object之间抉择时,你的工作就是挑出行为正确的那个。让编译器去‘尽可能的降低成本’”。
以上.
条款22:将成员变量声明为private
切记将成员变量声明为private. 这可赋予客户访问数据的一致性,可细微划分访问控制,允许约束条件获得保证,并提供class作者以充分的实现弹性。
protected并不比public更具封装性。
以上.
条款23:宁以non-member、non-friend替换、member函数
这样做可以增加封装性、包裹弹性和机能扩充性。
namespace WebBrowesrStuff {
class WebBrowesr {
public:
void clearCache();
void clearHistory();
void removeCooies();
};
void clearBrowser(WebBrowesr& wb) { // 而不是WebBrowesr提供一个member函数
wb.clearCache();
wb.clearHistory();
wb.removeCooies();
}
}
以上.
条款24:若所有参数皆需类型转换,请为此采用non-member函数
class Rational {
public:
Rational(int numerator = 0, int denominator = 1); // 构造函数可以不为explictit,允许int-to-Rational隐式转换
int numerator() const;
int denominator() const;
const Rational operator* (const Rational& rhs) const;
};
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneEighth; // 很好
result = result * oneEighth; // 很好
result = oneHalf * 2; // 很好 result = oneHalf.operator*(2);
result = 2 * oneHalf; // 错误!result = 2.operator*(oneHalf); 一目了然
换一种做法
const Rational operator* (const Rational& lhs, const Rational& rhs) const {
return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
Rational oneFourth(1, 4);
Rational result;
result = oneFourth * 2; // 正确
result = 2 * oneFourth; // 正确
以上.
条款25:考虑写出一个不抛异常的swap函数
1.default swap 将两个对象的值彼此赋值给对方。缺省的情况下swap可由程序标准库提供的swap算法完成。
namespace std {
template<typename T>
void swap(T& a, T& b) { // std::swap的典型实现
T temp(a); // 置换a和b的值
a = b;
b = temp;
}
}
2.std::swaps特化版本
如果你希望交换的是这样的对象,pimpl手法(以指针指向一个对象,内含真正的数据)
class WidgetImpl {
public:
private:
int a, b, c; // 可能有许多数据
std::vector<double> v; // 意味复制时间很长
};
class Widget {
public:
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs) { // 复制Widget时,复制其WidgetImpl
*pImpl = *(rhs.pImpl);
}
private:
WidgetImpl* pImpl;
};
一旦要置换两个Widget的值,我们要做的其实只是置换其pImpl指针。而调用std::swap它不只复制三个Widget,还复制三个WidgetImpl对象。非常缺乏效率。
所以这样的做法更适合我们:
namespace std {
template<> // 表示这是std::swap的一个全特化版本
void swap<Widget>(Widget& a, Widget& b) { // 表示这一特化版本针对“T是Widget设计”
swap(a.pImpl, b.pImpl);
}
}
这里要注意一点:通常我们不被允许改变std命名空间内的任何东西,但是可以为标准的templates制造特化版本。正如上面的例子。
3.member swap
但是通常当a和b想要访问的成员都是private,所以我们可以再改变上面的方法,把它实现为public member function
class Widget {
public:
void swap(Widget& other) {
using std::swap;
swap(pImpl, other.pImpl);
}
};
namespace std {
template<>
void swap<Widget>(Widget& a, Widget& b) {
a.swap(b);
}
}
4.non-member swaps
假设Widget和WidgetImpl都是class templates而非classes
template<typename T>
class WidgetImpl {...};
template<typename T>
class Widget {...};
所以按照上面的例子我们这样做:
namespace std {
template<>
void swap<Widget<T> >(Widget& a, Widget& b) { // 错误!不合法!
a.swap(b);
}
}
我们企图偏特化一个function template(std::swap), 但是C++只允许对class templates偏特化。
然后很自然的我们想到去重载std::swap,像这样:
namespace std {
template<>
void swap(Widget<T>& a, Widget<T>& b) {
a.swap(b);
}
}
一般而言,重载function templates没有问题,但是std是个特殊的命名空间,上面我们说过,可以全特化std内的templates,但是不要添加新的templates、classes或functions或其他的东西到std里。
我们可以这样做:
namespace WidgetStuff {
template<typename T>
void swap(Widget<T>& a, Widget<T>& b) { // non-member swap函数,这里并不属于std命名空间
a.swap(b);
}
}
以上.
条款26:尽可能延后变量定义式的出现时间
这样做可以增加程序的清晰度并改善程序效率。
1.不只应该延后变量的定义到非得使用它的时候,这还不够,可以延后到这份定义赋初值的时候(避免多余的default构造行为)。
对比下面两个做法就可以理解:
std::string encryptPassword(const std::string password) {
std::string encrypted; // 1次default构造
encrypted = password; // 1次赋值操作
encrypt(encrypted);
return encrypted;
}
std::string encryptPassword(const std::string password) {
std::string encrypted(password); // 1次copy构造
encrypt(encrypted);
return encrypted;
}
2.循环里的构造
Widget w; // 1次构造,1次析构,n次赋值
for (int i = 0; i < n; ++i) {
w = ...;
}
for (int i = 0; i < n; ++i) { // n次构造,n次析构
Widget w(...);
}
可以根据具体情况选择哪一种方法。
以上.
条款27:尽量少做转型动作
1.如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts, 如果有个设计需要转型动作,
试着发展无需转型的替代设计。
2.如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需要将转型放进让们自己的代码内。
3.宁可使用C++-style(新式)转型,不要使用旧式转型。这样很容易被辨识出来,且也比较有这分门类别的执掌。
以上.
条款28:避免返回handles指向对象内部成分
注:避免返回handles(包括references、指针、迭代器)指向对象内部。可以增加封装性。
1.帮助const成员函数更像const
class Point {
public:
Point(int x, int y);
void setX(int newVal);
void setY(int newVal);
};
struct RectData {
Point ulhc; // 左上角upper left-hand corner
Point lrhc; // 右下角lower right-hand corner
};
class Rectangle {
public:
Point& upperLeft() const { return pData->ulhc; }
Point& lowerRight() const { return pData->lrhc; }
private:
std::tr1::shared_ptr<RectData> pData;
};
Point coord1(0, 0);
Point coord2(100, 100);
const Rectangle rec(coord1, coord2);
rec.upperLeft().setX(50); // !!!rec应该是const才对
upperLeft调用者能够使用其返回的reference(指向rec内部的Point成员变量)来更改成员,但是原本rec应该是const。
然后我们通常会这样做:
class Rectangle {
public:
const Point& upperLeft() const { return pData->ulhc; }
const Point& lowerRight() const { return pData->lrhc; }
}
这样确保了返回的Points是不可被更改的。但是可能在其他场合带来问题。如下:
2.dangling handles(空悬的号码牌):这种handls所指东西(的所属对象)不复存在。
class GUIObject {...};
const Rectangle boundingBox(const GUIObject& obj);
GUIObject* pgo;
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft()); // !!!
这里boundingBox(*pgo)返回临时变量Rectangle,对这个临时变量upperLeft()函数返回的Point取地址,
然后这个临时变量析构时,pUpperLeft也就变成野指针了。
以上.
条款29:为“异常安全”而努力是值得的
//TODO:
条款30:透彻了解inlining的里里外外
1.将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
2.不要只因为function templates出现在头文件,就将他们声明为inline。
以上.
条款31:将文件间的编译依存关系降到最低
以上.