effective C++: 5实现(二)

2014-11-24 03:12:35 · 作者: · 浏览: 2
indow::onResize();//调用Window::onResize作用于*this身上
...
}
};
在探究dynamic_cast设计意涵之前,值得注意的是,dynamic_cast的许多实现版本执行速度相当慢。假如至少有一个很普通的实现版本基于“class名称之字符串比较”,如果你在四层深的单继承体系内的某个对象身上执行dynamic_cast,可能会耗用多达四次的strcmp调用,用以比较class名称。深度继承或多重继承的成本更高!某些实现版本这样做有其原因(它们必须支持动态链接)。在对注重效率的代码中更应该对dynamic_cast保持机敏猜疑。之所以需要用dynamic_cast,通常是因为你想在一个你认定为derived class对象身上执行derived class操作函数,但你的手上只有一个“指向base”的pointer或者reference,你只能靠他们来处理对象。两个一般性做法可以避免这个问题:
一,使用容器,并在其中存储直接指向derived class 对象的指针(通常是智能指针),如此便消除了“通过base class接口处理对象”的需要。假设先前的Window/SpecialWindow继承体系中有SpecialWindows才支持闪烁效果,试着不要这样做:
class Window{...};
class SpecialWindow:public Window{
public:
void blink();
};
typedef std::vector > VPW;
VPW winPtrs;
for (VPW::iterator iter = winPtrs.begin(); iter != vinPtrs.end(); ++iter)
{
if(SpecialWindow* psw = dynamic_cast (iter->get()))
psw->blink();
}
应该改而这样做:
typedef std::vector > VSPW;
VSPW winPtrs;
for (VSPW::iterator iter = winPtrs.begin(); iter != vinPtrs.end(); ++iter)
{
(*iter)->blink();
}
当然,这种做法无法在同一个容器内存储指针“指向所有可能之各种Window派生类”。如果需要处理多种窗口类型,你可能需要多个容器,他们都必须具备类型安全性(type-safe)。
另一种做法让你通过base class接口处理“所有可能之各种window派生类”,那就是在base class 里提供virtual函数做你想对各个Window派生类做的事。


虽然只有SpecialWindows可以闪烁,但或许将闪烁函数声明于base class内并提供一份什么也不做的缺省版本是有意义的:
class Window{
public:
virtual void blink(){}
};
class SpecialWindow:public Window{
public:
virtual void blink(){...};
};
typedef std::vector > VPW;
VPW winPtrs;
for (VPW::iterator iter = winPtrs.begin(); iter != vinPtrs.end(); ++iter)
{
(*iter)->blink();
}
无论哪一种写法,并非放之四海皆准,但在许多情况下它们都提供一个可行的dynamic_cast替代方案。当它们有此功效时,你应该欣然拥抱它们。
绝对必须拒绝的是所谓的“连串(cascading)dynamic_casts”:
typedef std::vector > VPW;
VPW winPtrs;
for (VPW::iterator iter = winPtrs.begin(); iter != vinPtrs.end(); ++iter)
{
if (SpecialWindow1 * psw1 = dynamic_cast (iter->get())){...}
else if (SpecialWindow2 * psw1 = dynamic_cast (iter->get())){...}
else if (SpecialWindow3 * psw1 = dynamic_cast (iter->get())){...}
...
}
这样产生出来的代码又大又慢,而且基础不稳,因为每次Window class集成体系一有改变,所有这一类代码都必须再次检阅看看是否需要修改。例如一旦加入新的derived class,或许上述连串判断中需要加入新的条件分支。这样的代码应该总是以某些“基于virtual函数调用”的东西取而代之。
优良的c++代码很少使用转型,我们应该尽可能隔离转型动作,通常是把它隐藏在某个函数内,函数的接口会保护调用者不受函数内部任何肮脏龌龊的动作的影响。

请记住:
1.如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计。
2.如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需要将转型放进他们自己的代码内。
3.宁可使用C++ style(新式)转型,不要使用旧式转型。前者很容易辨认出来,而且也有着比较分门别类的职责。




条款28:避免返回handle指向对象内部成分


假设程序涉及矩形。为了让Rectangle对象尽可能小,可能把定义矩形的点放在一个辅助的struct内再让Rectangle去指它:
class Point{
public:
Point(int x, int y);
...
void setX(int newVal);
void setY(int newVal);
...
};
struct RectData{
Point ulhc;
Point lrhc;
};
class Rectangle{
...
Point& upperLeft()const {return pData->ulhc;}
Point& lowerRight()const {return pData->lrhc;}
private:
std::tr1::shared_ptr pData;
};
这样的设计可以通过编译,但却是错误的。实际上自相矛盾,一方面upperleft和lowerRight被声明为const,不让客户修改Rectangle。另一方面,这两个函数都返回reference指向private数据,调用者可以通过这些reference更改内部数据:
Point coord1(0,0);
Point coord2(100,100);
const Rectangle rec(coord1, coord2);
rec.upperLeft().setX(50);//现在rec变成从(50,0)到(100,100)
第一,成员变量的封装性最多只等于“返回其reference”的函数的访问级别。
第二,如果const成员函数传出一个reference,后者所指数据与对象自身有关,而它又被存储在对象之外,那么这个函数的调用者可以修改那笔数据。这正是bitwise cons