第五章 实现
条款26:尽可能延后变量定义式的出现时间
只要你定义了一个变量而其类型带有一个构造函数或析构函数,那么当程序的控制流到达这个变量定义式时,你便得承受构造成本;当这个变量离开其作用域时,你便得承受析构成本。即使这个变量最终并为被使用,仍需耗费这些成本,所以应该尽量避免这种情形。
考虑下面这个函数,它计算同学密码的加密版本后而返回,前提是密码够长。如果密码短,函数会丢出一个异常,类型为logic_error:
//改函数过早定义encrypted std::string encryptPassword(const std::string& password){ using namespace std; string encrypted; if (password.length() < MinimumPasswordLength){ throw logic_error("Password is too short"); } ... return encrypted; }
如果有个异常被丢出,对象encrypted就没有被使用,但仍得付出encrypted的构造成本和析构成本。所以最好延后encrypted的定义式,直到确实需要它。
//延后encryted的定义,知道真正需要他 std::string encryptPassword(const std::string& password){ using namespace std; if (password.length() < MinimumPasswordLength){ throw logic_error("Password is too short"); } string encrypted; ... return encrypted; }
“通过默认构造函数构造出一个对象然后对它赋值”比“直接在构造函数时制定初值”效率差。
“尽可能延后”的真正意义是:你不只应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参为止。这样不仅能够避免构造和析构非必要对象,还可以避免无意义的default构造函数。
对于循环
//方法A:定义循环外 Widget w; for (int i = 0; i < n; ++i){ w = 取决于i的某个值; ... } //1个构造函数+1个析构函数+n个赋值操作; //方法B:定义循环内 for (int i = 0; i < n; ++i) { Widget w(取决于i的某个值); ... } //n个构造函数+n个析构函数
以上两周写法的成本:
做法A:1个构造函数+1个析构函数+n个赋值操作
做法B:n个构造函数+n个析构函数
如果classes的一个赋值成本低于一组构造+析构成本,做法A大体而言比较高效。尤其当n值很大的时候,否则做法B或许较好。此外做法A造成名称w的作用于比B大,有时对程序的可理解性和易维护性造成冲突。除非(1)你知道赋值成本比“构造+析构”成本低,(2)你正在处理代码效率高度敏感的部分,否则应该使用做法B。
请记住:
尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率。
条款27 :尽量少做转型动作
转型(casts)破坏了类型系统。那可能导致任何种类的麻烦,有些容易辨识,有些非常隐晦。
C++还提供四种新式转型:
const_cast通常被用来将对象的常量性转除。它也是唯一有此能力的C++-style转型操作符。
dynamic_cast主要用来执行“安全向下转型”,也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一可能消耗重大运行成本的转型动作。
reinterpret_cast意图执行低级转型,实际动作(及结果)可能取决于编译器,也就表示不可移植。例如将一个pointer to int转型为一个int。
static_cast用来强迫隐式转换,例如将non-const对象转为const对象,或将int对象转换为double等等。
任何一个类型转换往往真的令编译器编译出运行期间执行的码,例如将int型转换为double型几乎肯定会产生一些代码,因为在大部分计算机体系结构中,int的底层表述几乎不同于double的底层表述。但看下面的例子:
class Base{ ... }; class Derived :public Base{ ... }; Derived d; Base* pb = &d;
上述例子建立一个base class指针指向一个derived class对象,但有时候上述两个指针值并不相同。这种情况下会有个偏移量在运行期被施行与Derived*
指针身上,用以取得正确的Base*
指针值。
例子:很多应用框架中都要求derived classes内的virtual函数代码第一个动作就是先调用base class对于函数。假设有一个Window base class和一个SpecialWindow derived class,两者都定义了virtual函数onResize。进一步假设SpecialWindow的onResize函数被要求调用Window的onResize。下面实现方式之一看起来正确,实际错误。
class Window{ public: virtual void onResize() {...} ... }; class SpecialWindow:public Window{ public: virtual void onResize(){ // static_cast<Window>(*this).onResize();//错误 Window::onResize();//正确 ... } ... };
这段程序将*this转型为Window,对函数onResize的调用也因此调用了Window::onResize。但令人想不到的是,它调用的并不是当前对象上的函数,而是稍早转型动作所建立的一个“ this指针对象的bass class成分“的暂时副本身上的onResize。
之所以需要dynamic_cast,通常是因为你想在一个认定为derived class对象身上执行derived class操作函数,但你却只有一个“指向base”的pointer或reference。
请记住:
如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts。如果有个设计需要转型动作,试着发展无需转型的替代设计。
如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需要将转型放进他们自己的代码内。
宁可使用C++-style(新式)转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分们别类的职掌。
条款28:避免返回handles指向对象内部成分
1.例子:
using namespace std; class Point{ //用来描述“点” public: Point(int xVal, int yVal) :x(xVal), y(yVal){} void setX(int newVal){ x = newVal; } void setY(int newVal){ y = newVal; } int getX()const{ return x; } int getY()const{ return y; } private: int x; int y; }; struct RectData{ //用来描述“矩形” RectData(const Point& p1, const Point& p2) :ulhc(p1), lrhc(p2){} Point ulhc;//坐上 Point lrhc;//右下 }; class Rectangle{ //矩形类 public: Rectangle(RectData data) :pData(new RectData(data)){} Point& upperLeft()const{ return pData->ulhc; } Point& lowerRight()const{ return pData->lrhc; } private: shared_ptr<RectData> pData; }; int main(){ Point coord1(0, 0); Point coord2(100, 100); RectData data(coord1, coord2); const Rectangle rec(data); rec.upperLeft().setX(50); cout << rec.upperLeft().getX() << " " << rec.upperLeft().getY() << endl;//左上角坐标从(0,0)变为了(50,0) //破坏了private x的封装性 system("pause"); return 0; }
上述例子中upperLeft的调用者能够使用被返回的reference(指向rec内部的Point成员变量)来更改成员,但rec应该是不可变的(const)。我们可以得到以下两个结论:
a、变量的封装性最多等于“返回其引用”的函数的访问级别。
b、如果const成员函数传出一个reference,后者所指数据与对象自身有关联,而它又被储存于对象之外,那么这个函数的调用者可以修改那笔数据。
2、对象的引用、指针、迭代器都是所谓的handles,而返回一个“代表对象内部数据”的handle,会降低对象的封装性。在上述例子中,只要在upperLeft和lowerRight函数的返回类型加上const即可:
onst Point& upperLeft()const{ return pData->ulhc; } const Point& lowerRight()const{ return pData->lrhc; }
即便如此,upperLeft和lowerRight函数还是返回了代表对象内部的handles,有可能在其他场合带来问题,如它可能导致dangling handles,即这种handles所指东西不复存在。
请记住:
避免返回handles(包括引用,指针,迭代器)指向内部对象。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”(dangling handles)的可能性降至最低。
转自: