C++对象浅谈(续)

 

对象与异常

栈解退(stack-unwinding)

  • “栈”解退而非“堆”解退
  • “解退”的是 完整的 对象: 正在构造的对象不会被析构

写“异常安全(exception-safe)”的代码

牢记“异常无处不在”,每写一行代码都要考虑是否可能会抛出异常

for (;;){
delete p;
p = 0;
// this line is NOT useless
p = new Object();
// ...
}
避免构造函数泄漏资源

既然正在构造的对象不会被析构,构造函数申请的资源它自己要负责释放掉

AClass::AClass(){
applyResource();
ResourceClass res;
try {
// may throw
} catch (ex& ) {
freeResource();
// manually free resource
// nothing to be done for res, since it's dtor takes care of itself.
throw;
}
}
别让析构函数抛出异常

析构函数中抛出异常是一件很危险的事情:堆栈解退及异常处理模块中不允许再抛出任何异常。所以,析构函数应当不抛出任何异常,除非你已做好程序崩溃的心理准备。

~AClass{
try {
// ...
}
catch (...){
// catch every thing
// do nothing.
// or do something may not throw.
}
}
避免构造异常对象时抛出异常
  • 异常类构造函数不抛异常
  • “catch block”传引用
    try {
    // ...
    }
    catch (ex& ){
    // ...
    }
全局变量

全局变量除了“初始化顺序无法控制”、“操作流不明显”等一贯就有问题外,对 “异常安全性”也有影响:没有方法能处理全局对象构造函数抛出的异常。

“不抛出异常”的相对性

实际上很多操作都可能出错,很多函数都可能“throw”。但是我认为,我们讨论 “异常”应该限定在我们“想处理”的错误中。例如:理论上申请一片动态内存是有可能出错的,但是一般的应用都假定不会出现这种错误或者是即使出现了错误也不做任何处理,因此一般讨论的“异常”并不包含分配内存这种情况。在这个前提下,异常处理过程中因申请内存失败而抛出异常也就可以接受了。

对象的修改

最小权限原理

权限越大,“能”犯的错误就越多。所以有最小权限原则,即只赋予工作者尽量小的权限,理想情况下工作者具有的权限足够完成任务但又无法干其它任何事情。 C++的关键字“const”和C++风格强制类型转换就体现了这一点。

关键字 constmutable

const 修饰常量与形式参数

const 可以而且应当尽量用来修饰常量(或函数的形式参数)告诉编译器该数据不会改变。将常量用“const”标明的最大好处是可以在编译器就发现不期望的修改。而且也有助于代码优化——不论是编译器还是程序员自己进行优化。比如说“copy on write”。

const 对传值调用也有意义
void f(const int i){
++i;
//error: increment of read-only parameter `i'
}
基本类型的常量可以定义在头文件中

const常量默认为“internal linkage”,所以const常量定义在头文件中是合法的。但是这么做会导致每个包含该头文件的编译模块拥有一份该常量的拷贝。

一般来说,基本类型的常量(尤其是整型)可以定义在头文件中,好处是:

  • 可以确保各个编译模块中的常量保持一致。
  • 可以用整型常量来定义静态数组。

静态数组长度一般用整型字面常量或者相应的宏表示(T array[LEN])字面常量的缺点显而易见。宏应用在此处虽然没什么大问题但毕竟还是存在一定缺点。用整型常量相对来讲是最好的选择。可是由于定义静态数组时数组长度必须已知,所以整型常量的值必须在静态数组定义时可见。如果整型常量定义在另一个.cc文件中,就无法用该常量来定义数组。反之,如果该常量定义在头文件中且该头文件又被包含则可。

错误:

// a.h:
extern const int ARRAY_LEN;
// b.cc:
#include
"a.h"
const int ARRAY_LEN = 10;
// c.cc:
#include
"a.h"
int a[ARRAY_LEN];
// error

正确:

// a.h
const int ARRAY_LEN = 10;
// c.cc
#include
"a.h"
int a[ARRAY_LEN];
// ok
但是复合类型常量别定义在头文件中

例子(跳过?)

test.h
#include <iostream>
#ifndef TEST_H
#define TEST_H 1
using namespace std;

class
CA
{
public:
static int i;
CA() { ++ CA::i;}
};

const CA a;
#endif
// TEST_H
test1.cc
#include "test.h"
void f()
{
cout << a.i << endl;
}
test.cc
#include "test.h"
void f();
int CA::i = 0;

int main(int argc, char argv[]){
f();
cout <<a.i <<endl;
return 0;
}
compile & result (cygwin)
g++ test.cc test1.cc

result:
2
2
const 修饰成员函数

const 还可以用来修饰成员函数以表明它不会破坏对象的逻辑不变性。

物理不变与逻辑不变

逻辑不变(Logical Constness)指的是对象的呈现给用户的状态不变,但它的成员变量是否变化则不一定。与逻辑不变相对应的还有物理不变(Physical Constness)。所谓物理不变指的是对象的任何成员变量都不作任何改动。有时两者是一致的,但有很多时候两者并不一致。例如:假设有如下多线程环境下的set类,它的成员函数getData()获取指定键值的元素。

例子: MtSet

template <typename _K_,
typename _Compare_=
std::less<_K_> >
class MtSet{
public:
_K_ getData(const key_type& key) const{
MutexLock lock(_mutex_);
return (_set_.find(key));
}

public:
std::set<key_type> _set_;
mutable Mutex _mutex_;
// ...
};

显然,从逻辑上说getData()不修改调用它的对象,即不破坏对象的逻辑不变性;但是为了线程同步,getData()必然要先锁住互斥锁(_mutex_),也就必然破坏对象的物理不变性。

关键字 mutable

上一节讲到const成员函数应当保持对象逻辑上不变。但是一个成员函数被定义成 const成员后,编译器禁止它修改对象的任何属性。如果成员函数确实需要在不破坏对象逻辑不变性的前提下修改某一属性就需要借助关键字 mutable 了。

关键字 mutable 表示被修饰者在任何情况下都不为常量。上例中MtSet把_mutex_定义成mutable变量。不论MtSet的对象是否为常量,_mutex_都是一个“变”量。

C++风格的强制类型转换

static_cast, const_cast, dynamic_cast 和 reinterpret_cast

细分应用场景,增加检查
// compiles ok but error=prone
unsigned char u2 = (unsigned char)("hello");

// compiler find out the error for you
unsigned char u0 = reinterpret_cast<unsigned char>( "hello");

// correct one
const unsigned char u1 =
reinterpret_cast<const unsigned char>("hello");
语法形式上更明显
少用强制类型转换

尽管相对于C,C++的强制类型转换有改进,仍然建议尽量不要用强制类型转换。这也是C++ 强制类型转换的语法故意设计得如此繁琐的原因之所在:提醒大家少用它。

当然,如果必须要用,用C++风格的,别用C风格的。

参考书目

  1. The C++ Programming Language (Special Edition) by Bjarne Stroustrup
  2. C++ Standard Library: A Tutorial and Reference by Nicolai M. Josuttis
  3. C++ Primer (3rd Edition) by Stanley B. Lippman and Josée Lajoie
  4. Generic Programming and the STL: Using and Extending the C++ Standard Template Library by Matthew H. Austern
  5. Thinking in C++, Volume 1: Introduction to Standard C++ (2nd Edition) by Bruce Eckel
  6. Data Structures, Algorithms, and Applications in C++ by Sartaj Sahni
  7. Exceptional C++ by Hurb Sutter
  8. More Effective C++ by Scott Meyers
  9. Effective C++ by Scott Meyers
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值