Effective C++ - Implementations

本文探讨了C++编程中的一些关键实践,包括延后变量定义以优化性能,尽量避免转型以保持类型安全,理解转型的运行时成本,避免返回对象内部成分的引用以增强封装性和异常安全性,以及追求异常安全的代码设计。文章还讨论了inlining的权衡以及减少编译依赖的重要性,提出了handle classes和interface classes的概念及其优缺点。
摘要由CSDN通过智能技术生成

前言:实现中需要注意的一些问题。


1 尽可能延后变量的定义

只要你定义了一个变量,而其类型带有一个构造函数或析构函数,那么当程序的控制流到达这个变量定义式时,你便要承受构造成本;当这个变量离开作用域时,便要承受析构成本。

例子:

方法A

Widget w;
for (int i = 0; i < n; ++i) {
    w = 取决于i的某个值;
    // ...
}

方法B

for (int i = 0; i < n; ++i) {
    Widget w(取决于i的某个值);
    // ...
}

上面两种方法,哪种好?
方法A:1个构造函数 + 1个析构函数 + n个赋值操作
方法B:n个构造函数 + n个析构函数

因此,除非你知道赋值成本构造+析构成本低,否则,你应该使用方法B。

2 尽量少做转型动作

C++规则的设计目标之一是,保证”类型错误”绝不可能发生。理论上,如果你的程序很”干净地”通过编译,就表示它并不企图在任何对象身上执行任何不安全,无意义,愚蠢荒谬的操作。这是一个极具价值的保证,可别草率地放弃它

不幸的是,转型(cast)破坏了类型系统。那可能导致任何种类的麻烦,有些容易识别,有些非常隐晦。在C++中转型是一个你会想带着极大尊重去亲近的一个特性。(意思是,坑比较多)

转型语法

  1. 旧式转型(C风格)
(T) expression;   // 将expression转型为T
T(expression);    // 同上
  1. C++的风格
// 通常被用来将对象的常量性移除(cast away the constness)
const_cast<T>(expression);

// 主要用来执行"安全向下转型"(safe downcasting),也就是用来决定某对象是否归属继承体系中的某个类型。它是唯一无法由旧式转型执行的动作,也是唯一可能耗费重大运行成本的转型动作
dynamic_cast<T>(expression);

// 低级转型。实际动作及结果,可能取决于编译器,也就表示它不可移植
reinterpret_cast<T>(expression);

// 用来强迫隐士转换(implicit conversions)。例如,将non-const对象转为cosnt对象,或将int转为double。但是,它无法将const转换为non-const,这个只有const_cast才能办到
static_cast<T>(expression);

旧式转型仍然合法,但新式转型更受欢迎 。原因是:
* 它们很容易在代码中识别出来,不论是人工识别还是使用工具如grep,因此可以简化”找出类型系统在哪个点被破坏的过程”。
* 各转型动作的目标愈窄化。编译器可能诊断出错误的运用。例如,如果你打算将常量性去掉,除非使用新式转型中的const_cast,否则无法编译通过。

注意:许多程序员认为转型其实什么都没做,只是告诉编译器把某种类型视为另一种类型。这是错误的观念。任何一个类型转换(不论是通过转型操作而进行的显示转换,或通过编译器完成的隐式转换),往往真的令编译器编译出运行期间执行的代码。

例子:

class Base { // ... };
class Derived: public Base { // ... };

Derived d;
Base* pb = &d; // 隐式地将Derived* 转换为Base*

这里建立了一个base class指针指向一个derived class对象,但有时候上述的两个指针值并不相同。这种情况下,会有一个偏移量在运行期被施行于Derived*指针身上,用以取得正确的Base*指针值。

上面这个例子表明:单一对象(例如,一个类型为Derived的对象)可能拥有一个以上的地址(例如,以Base*指向它时的地址和以Derived*指向它时的地址)。C,Java,C#都不可能发生这种事,但C++可以。实际上,一旦使用多重继承,这事几乎一直发生着。即使在单一继承中也可能发生。意味着,你通常应该避免做出“对象在C++中如何布局”的假设。当然更不该以此假设为基础执行任何转型动作。例如,将对象地址转型为char*指针然后在它们身上进行指针算术,这几乎总是会导致无定义不明确的行为。

尽量避免使用dynamic_cast

之所以需要dynamic_cast,通常是因为你想在一个你认定为derived class对象身上执行derived class操作函数,但是你手上却只有一个”指向base”的pointer或reference。你只能靠它们来处理对象。

两个方法可以避免这个问题:

  1. 使用容器并在其中存储直接指向derived class对象的指针(通常是智能指针),如此便消除了“通过base class”接口处理对象的需要。(但是,这种做法使你无法在同一个容器内存储指针,指向所有可能之各种派生类,如果真要处理多种派生类对象,那就需要多个容器)

  2. 在base class内提供virtual函数做你想对各个派生类做的事,即,虚函数的方法。

例如:

class Base {
public: 
    virtual void dosomething() {} // 空实现
};

class Derived : public Base {
public:
    virtual void dosomething() {
        // 真正的实现
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值