Effective C++笔记: 实现(一)

 

Item 26: 只要有可能就推迟变量定义

 只要有可能(as long as possible的含义:你不仅应该推迟一个变量的定义直到你不得不用它之前的最后一刻,而且应该试图推迟它的定义直到你得到了它的初始化参数。通过这样的做法,你可以避免构造和析构无用对象,而且还可以避免不必要的缺省构造。更进一步,通过在它们的含义已经非常明确的上下文中初始化它们,有助于对变量的作用文档化。

 

比较特殊的例子是循环,如果一个变量仅仅在一个循环内使用,是循环外面定义它并在每次循环迭代时赋值给它更好一些,还是在循环内部定义这个变量更好一些呢?也就是说,下面这两个大致的结构中哪个更好一些?

// Approach A: define outside loop   // Approach B: define inside loop

Widget w;
for (int i = 0; i < n; ++i){         for (int i = 0; i < n; ++i) {
 
w = some value dependent on i;       Widget w(some value dependent on i);
  ...                                  ...
}                                    }

这里我将一个类型 string 的对象换成了一个类型 Widget 的对象,以避免对这个对象的构造、析构或赋值操作的成本的任何已有的预见。

对于 Widget 的操作而言,就是下面这两个方法的成本:

·    方法 A1 个构造函数 + 1 个析构函数 + n 个赋值。

·    方法 Bn 个构造函数 + n 个析构函数。

对于那些赋值的成本低于一个构造函数/析构函数对的成本的类,方法 A 通常更高效。特别是在 变得很大的情况下。否则,方法 B 可能更好一些。此外,方法 A 与方法 B 相比,使得名字 w 在一个较大的区域(包含循环的那个区域)内均可见,这可能会破坏程序的易理解性和可维护性。因此得出以下结论:除非你确信以下两点:(1)赋值比构造函数/析构函数对成本更低,而且(2)你正在涉及你的代码中的性能敏感的部分,否则,你应该默认使用方法 B

总结:

只要有可能就推迟变量定义。这样可以增加程序的清晰度并提高程序的性能。

Item 27: 将强制转型减到最少

旧式风格的转型:

C 风格(C-style)强制转型如下:

(T) expression                      // cast expression to be of type T

函数风格(Function-style)强制转型使用这样的语法:

T(expression)                       // cast expression to be of type T

这两种形式之间没有本质上的不同,它纯粹就是一个把括号放在哪的问题。我把这两种形式称为旧风格(old-style)的强制转型

 

C++ 同时提供了四种新的强制转型形式(通常称为新风格的或 C++ 风格的强制转型):

const_cast<T>(expression)
dynamic_cast<T>(expression)
reinterpret_cast<T>(expression)
static_cast<T>(expression)

每一种适用于特定的目的:

·    const_cast 一般用于强制消除对象的常量性。它是唯一能做到这一点的 C++ 风格的强制转型。

·    dynamic_cast 主要用于执行安全的向下转型(safe downcasting,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。它是唯一不能用旧风格语法执行的强制转型。也是唯一可能有重大运行时代价的强制转型。(过一会儿我再提供细节。)

·    reinterpret_cast 是特意用于底层的强制转型,导致实现依赖(implementation-dependent)(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制转型在底层代码以外应该极为罕见。在本书中我只用了一次,而且还仅仅是在讨论你应该如何为裸内存(raw memory)写一个调谐分配者(debugging allocator)的时候(参见 Item 50)。

·    static_cast 可以被用于强制隐型转换(例如,non-const 对象转型为 const 对象(就像 Item 3 中的),int 转型为 double,等等)。它还可以用于很多这样的转换的反向转换(例如,void* 指针转型为有类型指针,基类指针转型为派生类指针),但是它不能将一个 const 对象转型为 non-const 对象。(只有 const_cast 能做到。)

尽量使用C++风格的转型。

dynamic cast dynamic_cast 的实现都相当慢。例如,至少有一种通用的实现部分地基于对类名字进行字符串比较。如果你在一个位于四层深的单继承体系中的对象上执行 dynamic_cast,在这样一个实现下的每一个 dynamic_cast 都要付出相当于四次调用 strcmp 来比较类名字的成本。对于一个更深的或使用了多继承的继承体系,付出的代价会更加昂贵。一些实现用这种方法工作是有原因的(它们不得不这样做以支持动态链接)。尽管如此,除了在普遍意义上警惕强制转型外,在性能敏感的代码中,你应该特别警惕 dynamic_casts

在必须使用dynamic cast的地方,我们应该以某些“基于virtual函数调用”的东西来取代。例如:

class Window { ... };

class SpecialWindow: public Window {
public:
  void blink();
  ...
};
typedef                                            // see
Item 13 for info
  std::vector<std::tr1::shared_ptr<Window> > VPW;  // on tr1::shared_ptr

VPW winPtrs;

...

for (VPW::iterator iter = winPtrs.begin();         // undesirable code:
     iter != winPtrs.end();                        // uses dynamic_cast
     ++iter) {
  if (SpecialWindow *psw =
dynamic_cast<SpecialWindow*>(iter->get()))
     psw->blink();
}

应该被替换为:

通过一个基类的接口操控所有可能的 Window 派生类,就是在基类中提供一个让你做你想做的事情的虚函数。例如,尽管只有 SpecialWindows blink,在基类中声明这个函数,并提供一个什么都不做的缺省实现或许是有意义的:

class Window {
public:
 
virtual void blink() {}                       // default impl is no-op;
  ...                                           // see
Item 34 for why
};                                              // a default impl may be
                                                // a bad idea

class SpecialWindow: public Window {
public:
 
virtual void blink() { ... };                 // in this class, blink
  ...                                           // does something
};

typedef std::vector<std::tr1::shared_ptr<Window> > VPW;

VPW winPtrs;                                    // container holds
                                                // (ptrs to) all possible
...                                             // Window types

for (VPW::iterator iter = winPtrs.begin();
     iter != winPtrs.end();
     ++iter)                                    // note lack of
  (*iter)->blink();                             // dynamic_cast

总结

避免强制转型的随时应用,特别是在性能敏感的代码中应用 dynamic_casts,如果一个设计需要强制转型,设法开发一个没有强制转型的侯选方案。

如果必须要强制转型,设法将它隐藏在一个函数中。客户可以用调用那个函数来代替在他们自己的代码中加入强制转型。

尽量用 C++ 风格的强制转型替换旧风格的强制转型。它们更容易被注意到,而且他们做的事情也更加明确。

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

假设你正在一个包含矩形的应用程序上工作。每一个矩形都可以用它的左上角和右下角表示出来。为了将一个 Rectangle 对象保持在较小状态,你可能决定那些点的定义的域不应该包含在 Rectangle 本身之中,更合适的做法是放在一个由 Rectangle 指向的辅助的结构体中:

class Point {                      // class for representing points
public:
  Point(int x, int y);
  ...

  void setX(int newVal);
  void setY(int newVal);
  ...
};

struct RectData {                    // Point data for a Rectangle
  Point ulhc;                        // ulhc = " upper left-hand corner"
  Point lrhc;                        // lrhc = " lower right-hand corner"
};

class Rectangle {
  ...

private:
  std::tr1::shared_ptr<RectData> pData;          // see
Item 13 for info on
};                                               // tr1::shared_ptr

由于 Rectangle 的客户需要有能力操控 Rectangle 的区域,因此类提供了 upperLeft lowerRight 函数。可是,Point 是一个用户定义类型,所以,留心 Item 20 关于在典型情况下,以传引用的方式传递用户定义类型比传值的方式更加高效的观点,这些函数返回引向底层 Point 对象的引用:

class Rectangle {
public:
  ...
  Point& upperLeft() const { return pData->ulhc; }
  Point& lowerRight() const { return pData->lrhc; }
  ...
};

这个设计可以编译,但它是错误的。实际上,它是自相矛盾的。一方面,upperLeft lowerRight 是被声明为 const 的成员函数,因为它们被设计成仅仅给客户提供一个获得 Rectangle 的点的方法,而不允许客户改变这个 Rectangle(参见 Item 3)。另一方面,两个函数都返回引向私有的内部数据的引用——调用者可以利用这些引用修改内部数据!例如:

Point coord1(0, 0);
Point coord2(100, 100);

const Rectangle rec(coord1, coord2);     // rec is a const rectangle from
                                         // (0, 0) to (100, 100)

rec.upperLeft().setX(50);                // now rec goes from
                                         // (
50, 0) to (100, 100)!

请注意这里,upperLeft 的调用者是怎样利用返回的 rec 的内部 Point 数据成员的引用来改变这个成员的。但是 rec 却被期望为 const

这直接引出两条经验。第一,一个数据成员被封装,但是具有最高可访问级别的函数还是能够返回引向它的引用。在当前情况下,虽然 ulhc lrhc 被声明为 private,它们还是被有效地公开了,因为 public 函数 upperLeft lowerRight 返回了引向它们的引用。第二,如果一个 const 成员函数返回一个引用,引向一个与某个对象有关并存储在这个对象本身之外的数据,这个函数的调用者就可以改变那个数据(这正是二进制位常量性的局限性(参见 Item 3)的一个副作用)。

因此,我们应该给返回值加上const约束:

class Rectangle {
public:
  ...
  const Point& upperLeft() const { return pData->ulhc; }
  const Point& lowerRight() const { return pData->lrhc; }
  ...
};

通过这个修改的设计,客户可以读取定义一个矩形的 Points,但他们不能写它们。这就意味着将 upperLeft upperRight 声明为 const 不再是一句空话,因为他们不再允许调用者改变对象的状态。至于封装的问题,我们总是故意让客户看到做成一个 Rectangle Points,所以这是封装的一个故意的放松之处。更重要的,它是一个有限的放松:只有读访问是被这些函数允许的,写访问依然被禁止。

总结

避免返回对象内部构件的句柄(引用,指针,或迭代器)。这样会提高封装性,帮助 const 成员函数产生 cosnt 效果,并将空悬句柄产生的可能性降到最低

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值