条款23:宁以non-member,non-friend替换member函数
请记住:宁可拿non-member non-friend函数替换member函数,这样做可以增加封装性,包裹弹性和机能扩展性
条款24:若所有参数皆需类型转换,请为此采用non-member函数
请记住:如果你需要为某个函数的所有参数(包括this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member
条款25:考虑写出一个不抛一场的swap函数
5 实现
1)太快定义变量可能造成效率上的拖延
2)过度使用转型可能导致代码变慢又难维护,又招来微妙难解的错误
3)返回对象内部数据之号码牌(handles)可能会破坏封装并留给客户虚吊号码牌(dangling handles);
4)未考虑一场带来的冲击则可能导致资源泄漏和数据败坏;
5)过度热心的inline可能引起代码膨胀;
6)过度耦合(coupling)则可能导致让人不满意的冗长build time
条款26:尽可能延后变量定义的出现时间
只要你定义了一个变量而其类型带有一个构造函数和析构函数,那么当程序的控制流到达这个变量定义时,你便得承受构造成本;当这个变量离开作用域时,你便得承受析构成本。或许你会认为,你不可能定义一个不使用的变量,那么请看:
std::string encryptPassword(const std::string& password){
using namespace std;
string encrypted;
if(password.length() < MinimumPasswordLength){
throw logic_error("password is too shart");
}
return encrypted;
}
对象encrypted在此函数中并非完全未被使用,但如果有个异常被丢出,它就真的没被使用。所以应该这样:
std::string encryptPassword(const std::string& password){
using namespace std;
if(password.length() < MinimumPasswordLength){
throw logic_error("password is too shart");
}
string encrypted;
//...
return encrypted;
}
但是这段代码仍然不够秾纤合度,因为encrypted虽然获得定义但却无任何实参作为初值,这意味着调用的是其默认构造函数,条款4解释了为什么通过默认构造函数构造出一个对象然后对它赋值比直接在构造时指定初值的效率差。
std::string encryptPassword(const std::string& password){
//...
std::string encrypted;
encrypt(encrypted);
return encrypted;
}
更受欢迎的做法是以password作为encrypted的初值,跳过毫无意义的默认构造函数:
std::string encryptPassword(const std::string& password){
//...
std::string encrypted(password);
encrypt(encrypted);
return encrypted;
}
这让我们联想起本条款所谓尽可能延后的真正意义。你不只应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够个他初值实参为止。如果这样,不仅能够避免构造和析构非必要对象,还可以避免无意义的默认构造行为。
关于循环:
。。。。。。
请记住:
尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率。
条款27: 尽量少做转型动作
cast破坏了类型系统,那可能导致任何种类的麻烦,有些容易辨识,有些非常隐晦。
C风格的cast动作看起来像这样:
(T)expression
函数风格的转型动作看起来像这样:
T(expression) (????????????)
两种形式并无差别,纯粹只是小括号的摆放位置不同而已。C++提供四种新式转型:
1.const_cast通常被用来将对象的常量性移除。
2.dynamic_cast主要用来执行安全向下转型,也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一可能消耗重大运行成本的转型动作。
3.reinterpret_cast意图执行低级转型,实际动作可能取决于编译器,这也就表示它不可移植。例如将一个pointer to int转型为一个int。这一类转型的低级代码以外很少见。本书只使用一次,那是在讨论如何针对原始内存写出一个调试用的分配器时,见条款50.
4.static_cast用来强迫隐士转换,例如将non-const对象转为const对象,或将int转为double等等。它也可以用来执行上述多种转换的反向转换,例如将void*转为typed指针,将pointer-to-base转为pinter-to-derived.但它无法将const转为non-const.
class Base{};
class Derived: public Base{};
Derived d;
Base* pb = &d;
这里我们不过是建立一个base class指针指向一个derived class对象,但有时候上述的两个指针值并不相同。这种情况下会有个偏移量在运行期间被施行于Derived*指针身上,用以取得正确的Base*指针值。
上述例子表明,单一对象可能拥有一个以上的地址(例如当以Base*指向它时的地址和以Derived*指向它时的地址)。??????????
另一个关于转型的有趣事情是:我们可能容易写出某些似是而非的代码,例如许多应用框架都要求derived classes内的virtual函数代码的第一个动作就先调用base class的对应函数。以下代码看起来对,但是是错误的:
class Window{
public:
virtual void onResize(){}
//...
};
class SpecialWindow: public Window{
public:
virtual void onResize(){
static_cast<Window>(*this).onResize();
//...
}
};
恐怕你没想到的是,它调用的并不是当前对象上的函数,而是稍早转型动作所建立的一个“*this对象之base class成分”的暂时副本身上的onResize(译注:函数就是函数,成员函数只有一份,调用起哪个对象身上的函数有什么关系呢?关键在于成员函数都有个隐藏的this指针,会因此影响函数操作的数据)(???????????)。所以如果Window::onResize修改了对象内容,当前对象其实没被改动,改动的只是副本。然而SpecialWindow::onResize内如果也修改对象,当前对象真的会被改动。这样的结果是:其base class成分的更改没有落实,而derived class成分的更改倒是落实了。(整段都没看懂)
所以应该这样写:
class SpecialWindow: public Window{
public:
virtual void onResize(){
Window::onResize();
//...
}
};
之所以需要dynamic_cast,通常是因为你想在一个你认定为derived class对象身上执行derived class操作函数,但你手上却只有一个指向base的pointer或reference。两个方法可以避免这个问题:
方法一:使用容器并在其中直接指向derived class对象的指针(通常是只能指针):
不应该这样做:
class Window{};
class SpecialWindow: public Window{
public:
void blink();
//...
};
typedef std::vector<std::shared_ptr<Window> > VPW;
VPW winPtrs;
for(VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter){
if(SpecialWindow* psw = dynamic_cast<SpecialWindow*>(iter->get())){
psw->blink();
}
}
应该这样做:
typedef std::vector<std::shared_ptr<SpecialWindow> > VPSW;
VPSW winPtrs;
for(VPSW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter){
(*iter)->blink();
}
方法二:该方法可以让你通过base class接口处理所有可能之各种Window派生类,那就是在base class内提供virtual函数做你想对各个Window派生类做的事。
class Window{
public:
virtual void blink() {} //do nothing
//...
};
class SpecialWindow: public Window{
public:
virtual void blink(){}; //do something
};
typedef std::vector<std::shared_ptr<Window> > VPW;
VPW winPtrs;
for(VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); iter++){
(*iter)->blink();
}
请记住:
1.如果可以,尽量避免cast,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要cast,试着发展无需转型的替代设计。
2.如果转型是必须的,试着将它隐藏在某个函数背后。客户随后可以调用该函数,而不需要将转型放进他们自己的代码中。
3.宁可使用C++-style转型,不要使用旧式转型,前者容易识别出来,而且也比较有着分门别类的职掌。