C++编程调试秘笈----读书笔记(6)
2013-01-09 00:19
225 查看
六、一些杂项
1、避免编写拷贝构造函数和赋值操作符,如果默认版本并不适用,可以考虑把拷贝构造函数和赋值操作符声明为私有,禁止类实例的复制
2、避免在析构函数中编写代码
需要析构函数的原因可能有好几个:
a、在基类中,可能需要声明虚拟析构函数,这样就可以使用一个指向基类的指针指向一个派生类的实例
b、在派生类中,并不需要把析构函数声明为虚拟函数,但是为了增加可读性,也可以这样做
c、可能需要声明析构函数并不抛出任何异常
下来讨论一下为什么析构函数应该是空的:
看看这个Student类,因为他在构造函数中获取了一些资源,所以需要在析构函数中释放掉。
但是现在问题来了,这个类的设计----每次添加一个表示个人描述的新元素(例如number_)时,都需要在析构函数中添加对应的清理代码,这就违背了“不要迫使程序员记住某些事情”的原则;另一个问题是,缺少安全检查,首先你的number_可能是要必须大于0的,这样还得在new的前面进行if判断,另外一个极端的情况:
因为在C中throw了一个异常,所以导致class C的析构函数没有被执行,这就导致了一些问题的产生
改成下面的形式设计就要好得多:
即使第二个安全检查抛出异常,执行name_的智能指针的析构函数仍然会被调用,并执行他的清理工作。另一个附带的好处是,我们并不需要操心把这些智能指针初始化为NULL,这是自动完成的。因此,可以看到构造函数抛出异常是一种潜在的危险行为;对应的析构函数将不会被调用,因此可能会存在问题,除非析构函数是空函数。
3、编写一致的比较操作符
常用的操作符有< > <= >= == !=,站在C++的角度,这些操作可以写成6个相互完全独立的函数,但是他们相互之间又有联系,比如x1>x2成立,x2<x1和x1>=x2也是成立的。所以,我们需要一些步骤来帮我实现这个目标(往之前的scpp_types.h上面添加):
测试代码(vs2012+win7环境):
4、使用标准C函数库的错误
C函数库中,在好几个方面都是不安全的,还可能导致程序崩溃
比如使用stdio.h中想sprintf()这样的函数可能会有下面的问题:
a、有些函数接受字符数组的指针(char *),如果向他们传递一个NULL而不是指向合法的C字符串的指针,将会崩溃(比如strlen(NULL);就可以秒掉程序)
b、有些函数将写入一个缓冲区,他们可能会改写越过缓冲区尾部的内存,从而导致不可预料的程序行为
.....
上面的问题,可以这样处理:
a、提供执行所有必要安全检查的版本,并按照与处理空字符串的相同方式处理NULL指针
b、对于不能牺牲字符串操作速度的应用程序,提供只在测试时才激活的临时安全检查的函数版本
......
但是,这些问题最好的解决方案是不用C字符串函数库,而是改用C++所提供的类,比如:
strlen(name)可以改用name.size(),对于strcpy()可以仅仅 使用字符串赋值操作符;strcat()或strncat()可以改用:
或者采用更简短的形式:
这些代码不仅更容易阅读,并且更安全,而且对于较长的字符串,他们的速度也快于strcat()!因为不存在需要分配和改写缓冲区
1、避免编写拷贝构造函数和赋值操作符,如果默认版本并不适用,可以考虑把拷贝构造函数和赋值操作符声明为私有,禁止类实例的复制
2、避免在析构函数中编写代码
需要析构函数的原因可能有好几个:
a、在基类中,可能需要声明虚拟析构函数,这样就可以使用一个指向基类的指针指向一个派生类的实例
b、在派生类中,并不需要把析构函数声明为虚拟函数,但是为了增加可读性,也可以这样做
c、可能需要声明析构函数并不抛出任何异常
下来讨论一下为什么析构函数应该是空的:
class Student { public: Student { if (!number_) { number_ = new int(age); } } ~Student() { if (number_) { delete number_; } } private: int* number_; };
看看这个Student类,因为他在构造函数中获取了一些资源,所以需要在析构函数中释放掉。
但是现在问题来了,这个类的设计----每次添加一个表示个人描述的新元素(例如number_)时,都需要在析构函数中添加对应的清理代码,这就违背了“不要迫使程序员记住某些事情”的原则;另一个问题是,缺少安全检查,首先你的number_可能是要必须大于0的,这样还得在new的前面进行if判断,另外一个极端的情况:
#include "stdafx.h" #include "iostream" #include "string" class A { public: A() { std::cout << "Creating A" << std::endl; } ~A(){ std::cout << "Destroying A" << std::endl; } }; class B { public: B() { std::cout << "Creating B" << std::endl; } ~B(){ std::cout << "Destroying B" << std::endl; } }; class C : public A { public: C() { std::cout << "Creating C" << std::endl; throw "Don't like C"; } ~C(){ std::cout << "Destroying C" << std::endl; } private: B b_; }; int _tmain(int argc, _TCHAR* argv[]) { try { C c; } catch (...) { std::cout << "Caught an exception" << std::endl; } return 0; }
因为在C中throw了一个异常,所以导致class C的析构函数没有被执行,这就导致了一些问题的产生
改成下面的形式设计就要好得多:
class Student { public: Student(const int number, const char* name) { SCPP_ASSERT(number > 0, "number must be large 0"); number_ = new int(number); SCPP_ASSERT(name != NULL, "name must be not null"); name_ = new std::string(name); } ~Student() { } private: Student(const Student& student); Student& operator = (const Student&); scpp::ScopedPtr<int> number_; scpp::ScopedPtr<std::string> name_; };
即使第二个安全检查抛出异常,执行name_的智能指针的析构函数仍然会被调用,并执行他的清理工作。另一个附带的好处是,我们并不需要操心把这些智能指针初始化为NULL,这是自动完成的。因此,可以看到构造函数抛出异常是一种潜在的危险行为;对应的析构函数将不会被调用,因此可能会存在问题,除非析构函数是空函数。
3、编写一致的比较操作符
常用的操作符有< > <= >= == !=,站在C++的角度,这些操作可以写成6个相互完全独立的函数,但是他们相互之间又有联系,比如x1>x2成立,x2<x1和x1>=x2也是成立的。所以,我们需要一些步骤来帮我实现这个目标(往之前的scpp_types.h上面添加):
#ifndef __SCCP_TYPES_H__ #define __SCCP_TYPES_H__ #include <ostream> #include "scpp_assert.h" template <typename T> class TNumber { public: TNumber(const T& x =0 ) :data_(x) { } operator T() const { return data_; } TNumber &operator = (const T& x) { data_ = x; return *this; } TNumber operator ++(int) { TNumber<T> copy(*this); ++data_; return copy; } TNumber operator ++() { ++data_; return *this; } TNumber& operator += (T x) { data_ += x; return *this; } TNumber& operator -= (T x) { data_ -= x; return *this; } TNumber& operator *= (T x) { data_ *= x; return *this; } TNumber& operator /= (T x) { SCPP_ASSERT(x != 0, "Attempt to divide by 0"); data_ /= x; return *this; } T operator / (T x) { SCPP_ASSERT(x != 0, "Attempt to divide by 0"); return data_ / x; } private: T data_; }; typedef long long int64; typedef unsigned long long unsigned64; typedef TNumber<int> Int; typedef TNumber<unsigned> Unsigned; typedef TNumber<int64> Int64; typedef TNumber<unsigned64> Unsigned64; typedef TNumber<float> Float; typedef TNumber<double> Double; typedef TNumber<char> Char; class Bool { public: Bool(bool x = false) :data_(x) { } operator bool () const { return data_; } Bool& operator = (bool x) { data_ = x; return *this; } Bool& operator &= (bool x) { data_ &= x; return *this; } Bool& operator |= (bool x) { data_ |= x; return *this; } private: bool data_; }; inline std::ostream& operator << (std::ostream& os, Bool b) { if (b) { os << "True"; } else { os << "False"; } return os; } #define SCPP_DEFINE_COMPARISON_OPERATORS(Class) \ bool operator < (const Class& that) const { return CompareTo(that) < 0; } \ bool operator > (const Class& that) const { return CompareTo(that) > 0; } \ bool operator ==(const Class& that) const { return CompareTo(that) ==0; } \ bool operator <=(const Class& that) const { return CompareTo(that) <=0; } \ bool operator >=(const Class& that) const { return CompareTo(that) >=0; } \ bool operator !=(const Class& that) const { return CompareTo(that) !=0; } #endif
测试代码(vs2012+win7环境):
#include "stdafx.h" #include "scpp_assert.h" #include "iostream" #include "scpp_vector.h" #include "scpp_array.h" #include "scpp_matrix.h" #include "algorithm" #include "scpp_types.h" #include "scpp_refcountptr.h" #include "scpp_scopedptr.h" #include "scpp_ptr.h" #include "string" #define STUDENTAGE 10 class Student { public: Student(int age) { SCPP_ASSERT(age > 0, "number must be large 0"); age_ = age; } int CompareTo(const Student& that) const { if (this->age_ > that.age_) { return 1; } else if(this->age_ == that.age_) { return 0; } else { return -1; } } SCPP_DEFINE_COMPARISON_OPERATORS(Student); private: int age_; }; int _tmain(int argc, _TCHAR* argv[]) { Student studentFirst(STUDENTAGE); Student studentSecond(STUDENTAGE); if (studentFirst >= studentSecond) { std::cout << "(studentFirst > studentSecond) && (studentFirst == studentSecond)" << std::endl; } return 0; }
4、使用标准C函数库的错误
C函数库中,在好几个方面都是不安全的,还可能导致程序崩溃
比如使用stdio.h中想sprintf()这样的函数可能会有下面的问题:
a、有些函数接受字符数组的指针(char *),如果向他们传递一个NULL而不是指向合法的C字符串的指针,将会崩溃(比如strlen(NULL);就可以秒掉程序)
b、有些函数将写入一个缓冲区,他们可能会改写越过缓冲区尾部的内存,从而导致不可预料的程序行为
.....
上面的问题,可以这样处理:
a、提供执行所有必要安全检查的版本,并按照与处理空字符串的相同方式处理NULL指针
b、对于不能牺牲字符串操作速度的应用程序,提供只在测试时才激活的临时安全检查的函数版本
......
但是,这些问题最好的解决方案是不用C字符串函数库,而是改用C++所提供的类,比如:
strlen(name)可以改用name.size(),对于strcpy()可以仅仅 使用字符串赋值操作符;strcat()或strncat()可以改用:
int _tmain(int argc, _TCHAR* argv[]) { std::ostringstream buffer; buffer << "zeng"; buffer << "raoli"; std::string result = buffer.str(); std::cout << "this result string is : " << result << std::endl; return 0; }
或者采用更简短的形式:
int _tmain(int argc, _TCHAR* argv[]) { std::string result = "zeng"; result += "raoli"; std::cout << "this result string is : " << result << std::endl; return 0; }
这些代码不仅更容易阅读,并且更安全,而且对于较长的字符串,他们的速度也快于strcat()!因为不存在需要分配和改写缓冲区
相关文章推荐
- C++编程调试秘笈----读书笔记(5)
- C++编程调试秘笈----读书笔记(4)
- C++编程调试秘笈----读书笔记(1)
- C++编程调试秘笈----读书笔记(2)
- C++编程调试秘笈----读书笔记(3)
- oracle 读书笔记
- 【编程珠玑】读书笔记 第十一章 排序
- C语言深度剖析读书笔记
- 《离散数学及其应用》读书笔记【三】计数
- 《HBase权威指南》读书笔记 第八章:架构,LSM树
- oracle 读书笔记 三
- 鸟哥的Linux私房菜(基础篇) 读书笔记
- 也是醉了,思维导图做读书笔记
- 读书笔记-java网络编程-3线程-线程调度
- 【深入PHP 面向对象】读书笔记(二) - 高级特性
- [C++]《深入探索C++面向对象模型》读书笔记
- 读书笔记——数据结构(1)关于递归
- 《C++primer》读书笔记(1)
- WebServices原理与研发实践——读书笔记1
- 《图解HTTP[上野宣]》读书笔记六-3