逝水流年

This is a blog to record my life, my work, my feeling …

More Effective C++读书笔记9

Item 9:使用析构函数防止资源泄漏

这一章我觉得可以算作是异常安全处理的一种情况,也就是说在异常发生的情况下,保证资源能被正确释放。

这里分为两种情况来讨论,一是指针操作 举个例子:

1
2
3
4
5
6
void myFucntion()
{
    Object* obj = getBack();
    obj->;doSomething();
    delete obj;
}

如果在obj->;doSomething()中发生异常,那么delete obj将不会执行,这是个很显然的泄露问题。想到的第一解决方法基本都是加上try…catch(),Ok那么让我们重新整理代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
void myFucntion()
{
    Object* obj = getBack();
    try
    {
        obj->;doSomething();
    }
    catch(...)
    {
        delete obj;
    }
    delete obj;
}

好吧,我承认,程序很丑陋,代码重复,那么继续改进。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void myFucntion()
{
    Object* obj = getBack();
    try
    {
        obj->;doSomething();
    }
    catch(...)
    {
        ...
    }
    finally
    {
        delete obj;
    }
}

Ok,问题已经解决。但是还不要高兴,满篇的try catch,如果有很多地方都需要加try catch,那么工作量也是非常惊人的,当然你也可以这样写,但是我觉得作为程序员就是要有“懒惰”的思想,要简化,越简单越好,代码越少出错的地方也就越少。那么你一定想到了,利用智能指针auto_ptr或者share_ptr来解决问题。Ok,尽管不推荐auto_ptr,但是老的代码中还是会出现的。

1
2
3
4
5
void myFucntion()
{
    share_ptr<Object*>; obj(getBack());
    obj->;doSomething();
}

Perfect!

二是对象操作

最常见的问题时windows中的各种handler的关闭泄露问题,我们可以针对handler进行一个封装,在析构函数中释放handler。

1
2
3
4
5
6
7
8
9
10
11
class WindowHandle
{
public:
    WindowHandle(WINDOW_HANDLE handle): w(handle) {}
    ~WindowHandle() { destroyWindow(w); }
    operator WINDOW_HANDLE() { return w; } // see below
private:
    WINDOW_HANDLE w;
    WindowHandle(const WindowHandle&);
    WindowHandle& operator=(const WindowHandle&);
};

More Effective C++读书笔记8

Item 8:理解各种不同含义的new和delete

new操作符在C++中做了两件事,1是在堆上分配足够的内存,2是调用类的构造函数。new操作法的这种行为是程序员无法改变的,唯一程序员可以去做的就是按自己的需求来修改内存分配,通过重载operator new函数(void* operator new(size_t size))来自定义内存的分配,但是在调用构造函数上面是由编译器来决定的。如果要重载自己的operator new函数,第一个参数必须为size_t。

例如:
1
Obeject* obj = new Object();

被编译器解析:

1.allocate memory 2.call Object()构造函数

关于placement new,就是在已经分配的内存上创建对象,例如:

1
2
3
4
void* placementnewSample(void* buffer, size_t)
{
    return new (buffer) Object();
}

注意,如果是调用的placement new,则对应的一定不要调用delete操作符,因为该对象是在buffer中分配的。可以直接调用对象的析构函数。数组的new操作符表现形式不同单个对象的new,但是做的事情是一样的。只不过是需要将所有数组元素都分配内存并依次调用构造函数。

1
void* operator new[](size_t size);

为new数组分配内存函数,可以被重载。delete操作符作为new操作符对应的相反行为,做的事情自然跟new相反,1调用析构函数,2释放已经分配的内存。如果你重载了operator new,则你必须重载operator delete来对应,否则有可能导致new和delete行为的不一致性。什么样的new对应什么样的delete,即单个对象的new对应单个对象的delete,new[]对应delete[]。注意,尽量不要重载全局的new和delete,这样会造成程序的不兼容性,除非这是你想要的结果。

More Effective C++读书笔记7

Item 7:不要重载“&&”,“||”, 或“,”

C/C++中“&&”和“||”操作符是采用短路计算法的,而且是有顺序的。即从左往右计算,如果一旦确认表达式的值,那么后面的表达式或计算不再执行。而重载运算符本质上是函数重载,即运算符操作为函数操作。operator &&()有两种情况,一是全局重载,二是作为类成员函数;如果是全局重载,那么调用本质为 “operator &&(expression1, expression2)”,而C++并没有规定参数的计算顺序,所以没有办法保证重载的&&保持原意不变。成员函数形式为expression1.operator&&(expression2),跟全局问题一样,所以“&&”和“||”操作符适合重载。“,”运算符也是相同的原因,这里不再赘述。

操作符重载的目的是为了程序更容易阅读,理解和书写。

More Effective C++读书笔记6

Item 6:自增(increment)、自减(decrement)操作符前缀形式与后缀形式的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class UPInt
{
public:
    ...
    UPInt& operator++();         //前缀形式
    const UPInt operator++(int); //后缀形式
    UPInt& operator--();         //前缀形式
    const UPInt operator--(int); //后缀形式
};
UPInt& UPInt::operator++()
{
    *this += 1;
    return *this;
}
const UPInt UPInt::operator++(int)
{
    UPInt temp = *this;
    ++(*this);
    return temp;
}
...

从代码中可以看到,

  1. 前缀是先增加再返回,而后缀是先返回再增加。

  2. 前缀和后缀在表现形式上就有所不同,是通过参数来区分的,这是C++语言规范规定的区别标准。

  3. 二者在返回值上也不相同,前缀形式返回类型的引用,而后缀则返回const 类型。为什么二者返回类型不相同,是为了防止出现 UPInt u; u++++;(在基础类型中不合法)。为了保持和基础类型一致而采取的考虑。

  4. 不管是前缀形式还是后缀形式,二者最终行为都是一致的(即增1),后缀实现采用前缀实现,这样就缩小改变,只需用改变前缀的实现就可以二者都改变。

  5. 从实现上可以看出,后缀形式比前缀形式多了一个临时对象temp,而临时对象会造成构造和析构的时间开销,空间方面也会有开销,这就导致前缀形式的效率会比后缀要高,所以能使用前缀形式就要使用前缀形式。

More Effective C++读书笔记5

Item 5:谨慎定义类型转换函数

隐式类型转换时在编译期间由编译器来执行的。除了C/C++默认的基本类型的隐式类型转换外,用户自定义类型的隐式转换方式有两种:

1.定义隐式类型转换运算符

2.单参数构造函数或者多参数构造函数,但第一个参数以后的参数都有默认值。

隐式类型转换运算符例子:

1
2
3
4
5
6
7
8
class Rational
{
public:
    Rational(int numerator = 0, int denominator = 1);
    operator double() const;   //不需要返回类型,函数名称就是返回类型
};
Rational r(1, 2);
double d = 0.5 * r; //r 转换为double类型

标题是要谨慎定义类型转换函数,为什么要谨慎呢,就是说这些定义的类型转换函数有可能会产生不是你想要的结果,比如你不想进行转换的时候,却给你转换了。如下情况:

cout << r;  //Rational并没有定义operator << 运算符,但是,编译器会找到operator double()转换函数,将r转换为double输出。但这并不是你想要的结果。

解决方法是用不使用语法关键字的等同的函数来替代转换运算符。如定义一个toDouble()函数来代替operator double()转换函数。STL中的string就提供了一个c_str()函数来转换到char*。

通过单参数构造函数进行隐式类型转换更难消除。而且在很多情况下这些函数所导致的问题要甚于隐式类型转换运算符。

我们来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
temlplate<typename T>;
class Array
{
public:
    Array(int lowBound, int hightBound);
    Array(int size);
    T& operator[](int index);
};
Array<int>; a(10);
Array<int>; b(10);
for (int i = 0; i < 10; ++i)
{
    if (a == b[i]) // 哎呦! "a" 应该是 "a[i]"
    {
        do something for when
        a[i] and b[i] are equal;
    }
    else
    {
        do something for when they're not;
    }
}

我们的编译器注意到它能通过调用 Array;构造函数能转换 int 类型到 Array;类型,这个构造函数只有一个 int 类型的参数。然后编译器如此去编译,生成的代码就象这样:

1
if (a == static_cast< Array<int>; >;(b[i]))

解决方案是利用一个最新编译器的特性,explicit 关键字。

More Effective C++读书笔记4

Item 4:避免无用的缺省构造函数

没有缺省构造函数会导致:

1.无法创建该类的数组形式。

2.无法在许多基于模板的容器类里使用。

设计虚基类时所面临的要提供缺省构造函数还是不提供缺省构造函数的两难决策:

1.不提供缺省构造函数的虚基类,很难与其进行合作。因为几乎所有的派生类在实例化时都必须给虚基类构造函数提供参数。

2.提供无意义的缺省构造函数也会影响类的工作效率。

More Effective C++读书笔记3

Item 3: 不要对数组使用多态

语言规范中说通过一个基类指针来删除一个含有派生类对象的数组,结果将是不确定的。

1
2
3
4
5
6
7
8
9
class BST { ... };
class BalancedBST: public BST { ... };
void printBSTArray(ostream& s,const BST array[], int numElements)
{
    for (int i = 0; i < numElements; )
    {
        s << array[i]; //假设 BST 类重载了操作符<<
    }
}

编译器原先已经假设数组中元素与 BST 对象的大小一致,但是现在数组中每一个对象大小却与 BalancedBST 一致。派生类的长度通常都比基类要长。我们料想 BalancedBST 对象长度的比 BST 长。如果如此的话,printBSTArray 函数生成的指针算法将是错误的,没有人知道如果用 BalancedBST 数组来执行 printBSTArray 函数将会发生什么样的后果。不论是什么后果都是令人不愉快的。

值得注意的是如果你不从一个具体类(concrete classes) (例如 BST)派生出另一个具体类(例如 BalancedBST),那么你就不太可能犯这种使用多态性数组的错误。

More Effective C++ 读书笔记2

Item 2:尽量使用C++风格的类型转换

  1. C语言的类型转换缺点

    1.1 过于粗鲁,允许在任何类型之间进行转换。

    1.2 在程序语句中难以识别。

    1.3 类型转换失败不可获知。

2. C++类型转换符的介绍

2.1.static_cast 在功能上基本上与 C 风格的类型转换一样强大,含义也一样。但是,static_cast 不能从表达式中去除 const 属性。

2.2.const_cast 用于类型转换掉表达式的 const 或 volatileness 属性。

2.3.dynamic_cast,它被用于安全地沿着类的继承关系向下进行类型转换。在帮助你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型上。

2.4.reinterpret_cast,其的转换 结果几乎都是执行期定义(implementation-defined)。因此, 使用reinterpret_casts 的代码很难移植。 reinterpret_casts 的最普通的用途就是在函数指针类型之间进行转换。

More Effective C++ 读书笔记1

Item 1:指针和引用的区别

1.指针可以为空值,引用不可以引用不可以为空值的好处是可以省略判断,提高代码效率。

1
2
3
4
5
6
7
8
9
10
11
void Test(const  int& count)
{
    cout << count << endl;
}
void Test(const int* count)
{
    if(NULL != count)
    {
        cout << count << endl;
    }
}

2 指针可以被改变,引用初始化后不可以再改变

3 重载某些操作符时可能需要返回引用

也就是说,当有可能会为空值的时候要使用指针,当有变量可能改变的时候要使用指针。

2013年个人计划

2013个人计划:

一、专业书籍阅读清单

1.《代码大全》 2.《STL 源码解析》 3.《More Effective C++》—finished 4.《Effective STL》 5.《UNIX编程艺术》 6.《算法导论》 7.《移山之道》 8.《设计模式》

二、个人修养人文书籍

1.《暗时间》—finished 2.《不喊哎呦喊ouch》 3.《单反摄影入门》 4.《思考的艺术》 5.《程序员的数学》

三、锻炼身体

1.保证晚上12点之前睡觉 2.坚持每天室内锻炼,俯卧撑或者健身操,周末打羽毛球 3.每周跑步至少1小时

四、知识积累和分享,个人影响力

1.每周坚持写2-3篇技术博客或总结 2.坚持学习unix和linux,做到可以脱离windows

五、思考,感悟,实践,总结

1.三十而立,要在工作方法和生活中都能有质的提高;成为一个有责任敢担当的真正男人 2.和老婆远距离旅游一次