部分方法的实现设计问题

一、尽可能延后变量定义式的出现时间

1.跳过毫无意义的default构造过程

//方法1
std::string encrypted;
encrypted = password;

//修改版本
std::string encrypted(password);

2.for循环过程,

在以下方法中定义于循环外,将造成1次构造,1次析构,n次赋值运算。构造于循环内,将构造n次构造,n次析构。主要根据其中的赋值运算消耗进行运算。

Widget w;
for(int i = 0; i<n; ++i){
    w = 取决于;
}

二、尽量少做转型动作

1.旧时转型

T(expression)

2.新式转型

(1)const_cast(expression)

C++中唯一的用于转除对象的常量性

(2)dynamic_cast(expression)

安全向下转型,用来决定某对象是否归属集成体系中的某个类型。它是唯一无法用旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作

想在一个derived class对象身上执行derived class操作函数,但是手上只有一个”指向base“的pointer或reference。

(3)reinterpret_cast(expression)

意图执行低级动作,例如将一个pointer to int转型为一个int。

(4)static_cast(expression)

用来强迫隐式转换,例如将non-const对象转为const对象,或将int转为double,也可以执行void*转为typed指针,将pointer-to base转为pointer-to derived

doSomeWork(Widget(15));	//旧式风格
doSomeWork(static_cast<Widget(15)>);	//新式风格

int转为double会产生真实的代码

3.单一对象(例如一个类型为Derived的对象)可能拥有一个以上的地址(例如“以base*)指向它”和“以Derived*指向它

class Base{};
class Derived: public Base{};
Derived d;
Base* pb = &d;

4.转型动作有时候会因为理解错误导致代码出问题,如下代码所示,针对this对象的Window部分通过static_cast创建了一个副本,再对这个副本进行了onResize()的调用。

class Window{
public:
    virtual void onResize(){...}
    
};

class SpecialWindow: public Window{
public:
    virtual void onResize(){
        static_cast<Window>(*this).onResize();
        ...	//	SpecialWindow方法
    }
}

若想调用Window的onResize()方法作用在自己身上,修改如下:

Window::onResize();

5.使用dynamic_cast实现版本执行速度相当慢,针对一个类所使用时,会对其每一层的派生类进行strcmp调用。要十分注意其效率和速度

错误做法:直接转换,速度慢

class Window{...};
typedef std::vector<std::tr1::shared_ptr<Window>> VPW;
VPW winPtrs;
...
for(VPW::iterator iter = winPtrs.begin();
   iter != winPtrs.end(); ++iter){
    if(SpecialWindow * psw = dynamic_cast<SpecialWindow* >(iteer->get()))
}

(1)应对方法1,使用容器并在其中存储直接指向derived class对象的指针(智能指针)

class Window{...};
typedef std::vector<std::tr1::shared_ptr<SpecialWindow>> VPW;
VPW winPtrs;
...
for(VPW::iterator iter = winPtrs.begin();
   iter != winPtrs.end(); ++iter){
    (*iter)->blink();
}

(2)应对方法2,通过base class接口处理”所有可能之派生类“,在base class中声明缺省虚函数,在派生类声明具体实现

class Window{
public:
    virtual void blink(){ }	//	缺省虚函数
    
};

class SpecialWindow: public Window{
public:
    virtual void blink(){
        
        ...		//	实现
    }
};

typedef std::vector<std::tr1::shared_ptr<Window>> VPW;
VPW winPtrs;
...
for(VPW::iterator iter = winPtrs.begin();
   iter != winPtrs.end(); ++iter){
    (*iter)->blink();
}

三、 避免返回handles(号码牌)指向对象内部成分

1.当我们在const函数中返回了reference,很可能调用者能通过reference更改内部数据。指针和迭代器同样。

解决方法:可以声明该函数返回的类型也是const类型

2.使用handles还有可能导致指针空悬的问题。如下代码所示,在进行完语句1后,boundingBox(*pgo*).upperLeft()的对象将会自动进入析构函数,导致pUpperLeft指向的地址的内容为空

class GUIOject{...};
const Rectangle
    boundingBox(const GUIObject& obj);


GUIObject* pgo;
...
const Point* pUpperLeft = & (boundingBox(*pgo).upperLeft());	//	语句1

四、为“异常安全”而努力是值得的

1.使用互斥器的情况,以下方法中安全性很差。

(1)不泄露任何资源:在代码中如果在加锁后解锁前,出现了异常,将发生锁解不开的情况。

(2)不允许数据败坏:以上代码中如果new Image失败了,此时imageChanges++已经运行包括delete,但是其实新图像并没有被成功初始化

class PrettyMenu{
public:
    void changeBackground(std::istream& imgSrc);
private:
    Mutex mutex;
    Image* bgImage;
    int imageChanges;
};

void PrettyMenu::changeBackground(std::istream& imgSrc){
    lock(&mutex);
    delete bgImage;
    ++imageChanges;
    bgImage = new Image(imgSrc);
    unlock(&mutex);
}

2.异常安全有三种保证

(1)基本承诺:如果异常被抛出,程序内任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态。但是现实状态恐怕不可预料,因为有可能我们一旦抛出异常,PrettyMenu对象可以基于拥有原背景图像或者是一个初始化图像,但是客户无法预期是哪一种情况。

(2)强烈保证:如果异常被抛出,程序状态不改变。如果没被抛出就是完全成功。

(3)不抛掷保证:承诺绝不抛出异常,保证函数一定是正确的。

对以上代码的修改方法一,在以下代码中,tr1::shared_ptr::reset函数只有在新图像被成功创建后才会进行调用,在调用时才会自动在其内部运行原对象的delete函数。同时该方法也让代码进一步缩短,提高了安全性:

class PrettyMenu{
    std::tr1::shared_ptr<Image> bgImage;
};

void PrettyMenu::changeBackground(std::istream& imgSrc){
    Lock ml(&mutex);
    bgImage.reset(new Image(imgSrc));	//	通过“new Image”的执行结果设定bgImage内部指针
    ++imageChanges;
}

修改方法二,加入copy and swap方法,更容易保证强烈的安全性,但也会带来相应的时间和空间消耗。先给预修改的对象构建一个副本,针对副本进行修改,修改完成后再通过swap进行替换回去,哪怕这中间发生异常错误,原对象依然保持不变。

struct PMImpl{
    std::shared_ptr<Image> bgImage;
    int imageChanges;
}
class PrettyMenu{
    std::tr1::shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::istream& imgSrc){
    using std::swap;
    Lock ml(&mutex);
    std::tr1::shared_ptr<Image> pNew(new PMImpl(*pImpl))
    pNew->bgImage.reset(new Image(imgSrc));
    ++pNew->imagechages;
    swap(pNew, pImpl);
}

以上问题中,即使使用copy and swap方法不能总是带来强烈保证,在以下方法中,可能f1方法是强烈保证,但是f2发生了异常,而此时f1修改的内容因为已经修改我们需要事先记录才能回到原状态。

void someFunc(){
    ...	//	创建副本
    f1();
    f2();
    ...	//	将修改后的状态置换过来
}

五、透彻了解inlining的里里外外

1.inline函数会让“对此函数的每一个调用”都以函数本体替换它。因此可能会增加目标码的大小,但是如果是普通函数也会占用,不过占用不多,因此如果inline函数本体很小,编译器针对“函数本体”所产出的码可能比针对“函数调用”产出的码更小,因此才导致较小的目标码和较高的指令缓存装置集中率。

2.inline函数有隐喻方式和明确。

隐喻方式,通常为成员函数:

class Person{
public:
    int age() const{ return theAge; }	//	隐喻方式
private:
    int theAge;
}

明确方式,直接在函数前加inline关键字,比如max函数

3.inline通常不用用于针对virtual函数。inline是执行前,先将调用动作替换为被调用函数的本体,virtual是等待,直到运行期才确定调用哪个函数。

inline函数是否真是inline取决于建置环境,取决于编译器。大多数编译器在无法将要求的函数inline化时,会给一个警告信息。

4.有时候虽然编译器意愿inlining某个函数,还是可能为该函数生成一个函数本体。比如,如果程序要取某个inline函数的地址,编译器通常必须为此函数生成一个outlined函数本体,因为函数指针必须指向一个存在的函数,inline函数没有实际地址,直接做的替换操作。

编译器通常不对“通过函数指针而进行的调用”实施inlining。

inline void f(){...}
void (*pf) () = f;

...
    
    
f();	//	正常inline
pf();	//	不被inline

5.构造函数和析构函数不适合inlining,因为如果其有派生类或者继承类,其构造函数以及析构函数通常是一层嵌套一层的,如果声明为inline的话,会导致空间占用等等

6.对于Template应该多多考虑是否inline,因为Template有多种形式,这每种形式是否都能inline。inline通常发生在编译期,Template通常在编译器,有时也在连接期,在连接期的话将会导致异常。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值