条款1:视C++为一个语言联邦
条款2:尽量以const、enum、inline替换#define
原因1:
Briefly, it is the mapping of the name you assign a variable to its address in memory, including metadata like type, scope, and size. It is used by the compiler.
That's in general, not just C[++]*. Technically, it doesn't always include direct memory address. It depends on what language, platform, etc. the compiler is targeting.
原因2:
对float这类浮点数,如果使用const来替换define,可以减少代码替换量,不至于出现多份浮点数。
class专属常量
class A
{
public:
//class专属常量
static const int a;
A(){};
};
上面代码中static int a为声明式,而非定义式,只要不取它们的地址,可以只用声明它,但如果要取地址,则必须定义它
const int A::a = 10;
条款3:尽可能使用const
区分常量指针与指针常量
const int *p1 = &a; //常量指针,const data
int *const p2 = &b; //指针常量,const pointer
*p1 = 10; //ERROR
p1 = &b; //OK
*p2 = 10; //OK
p2 = a; //ERROR
const在iterator中的应用
vector<int> v;
//it1相当于一个T* const
const vector<int>::iterator it1 = v.begin();
*it1 = 10; //OK
++it; //ERROR
//it2相当于一个const T*
vector<int>::const_iterator it2 = v.begin();
*it2 = 10; //ERROR
it2++; //OK
const成员函数
bitwise constness 和 logical constness
条款4:确定对象被使用前已被初始化
列表初始化是发生于类成员调用它们的default构造函数时(比进入该类的构造函数本体的时间更早)
class的成员变量初始化总是以声明次序来进行初始化,即使在初始化列表中以不同的次序出现,也不会有影响。
不同编译单元内定义之non-local static 对象的初始化次序
条款5:了解C++默默编写并调用哪些函数
条款6:若不想使用编译器自动生成的函数,就该明确拒绝
class A
{
public:
A(){}
private:
A(const A&);
A &operator= (const A&);
};
class Uncopyable
{
public:
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
}
class A:private Uncopyable
{.....}
条款7:为多态基类声明virtual析构函数
原因
解决办法:
条款8:别让异常逃离析构函数
条款9:绝不在构造和析构过程中调用virtual函数
class A
{
public:
A();
virtual void getA() const = 0;
};
A::A()
{
...
getA();
}
class B:public A
{
public:
virtual void getA() const;
};
class C:public A
{
public:
virtual void getA() const;
};
B b;
当创建b时,B的构造函数会被调用,但首先A的构造函数会被先调用,在A的构造函数运行时最后会调用getA()函数,这时getA()是base class A的版本,并不是derived class B的版本,即
在base class构造期间,virtual函数不是virtual函数
条款10:令operator=返回一个reference to *this
条款11:在operator=中处理“自我赋值”
原因:由“别名(aliasing)”所引起,即有一个以上的方法指涉某对象,一般而言如果某段代码操作指针或引用而它们被用来“指向多个相同类型的对象”,那么就需要考虑这些对象是否为同一个a[i] = a[j]; //若i j相同,便为自我赋值
*p1 = *p2; //p1,p2可能指向同一个东西
class A {...};
class B:public A{...};
void func(const A &a,B *b); //a,*b可能是同一个对象
证同测试
A &A::operator= (const A &a)
{
if(this == &a)
return *this;
delete p;
p = new A(*a.p);
return *this;
}
A &A::operator= (const A &a)
{
A* pa = p; //记住原先的指针p
p = new A(a.p);
delete pa;
return *this;
}
另一种可以处理异常安全、自我赋值安全的解决方法是copy and swap:
class A{
...
void swap(A &a);
...
};
A &A::operator= (const A &a)
{
A temp(a);
swap(temp);
return *this;
}
条款12:复制对象时勿忘其每一个成分
class B{...};
class A
{
public:
A(const A& a);
A& operator =(const A& a);
private:
string s;
B b;
};
A此时若进行复制构造,那么将出现
局部拷贝的情况,因为未定义B的复制构造函数。
class C:public A
{
public:
C(const C& c);
C& operator =(const C& c);
private:
int x;
};
C::C(const C& c)
:A(c),x(c.x) {}
C& C::operator =(const C& c)
{
A::operator =(c);
x = c.x;
return *this;
}
条款13:以对象资源管理
A* createFactory(); //工厂函数
auto_ptr<A> p1(createFactory());
auto_ptr<A> p2(p1); //p2指向对象,p1为null
A* createFactory(); //工厂函数
shared_ptr<A> p1(createFactory());
shared_ptr<A> p2(p1); //p1,p2指向同一个对象
条款14:在资源管理类中小心copying行为
什么是RAII??
RAII是Resource Acquisition Is Initialization的简称,是C++语言的一种管理资源、避免泄漏的惯用法。利用的就是C++构造的对象最终会被销毁的原则。RAII的做法是使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,释放构造时获取的资源。
什么时候需要使用RAII?
new一个变量的时候可能会忘记delete,因此如果我们将构造、析构这一过程封装起来,即委托给一个对象来处理,那么就可以很好的解决这一问题。
当我们在一个函数内部使用局部变量,当退出了这个局部变量的作用域时,这个变量也就别销毁了;当这个变量是类对象时,这个时候,就会自动调用这个类的析构函数,而这一切都是自动发生的,不要程序员显示的去调用完成。这个也太好了,RAII就是这样去完成的。由于系统的资源不具有自动释放的功能,而C++中的类具有自动调用析构函数的功能。如果把资源用类进行封装起来,对资源操作都封装在类的内部,在析构函数中进行释放资源。当定义的局部变量的生命结束时,它的析构函数就会自动的被调用,如此,就不用程序员显示的去调用释放资源的操作了。
示例代码:
class ArrayOperation
{
public :
ArrayOperation()
{
m_Array = new int [10];
}
void InitArray()
{
for (int i = 0; i < 10; ++i)
{
*(m_Array + i) = i;
}
}
void ShowArray()
{
for (int i = 0; i <10; ++i)
{
cout<<m_Array[i]<<endl;
}
}
~ArrayOperation()
{
cout<< "~ArrayOperation is called" <<endl;
if (m_Array != NULL )
{
delete[] m_Array;
m_Array = NULL ;
}
}
private :
int *m_Array;
};
bool OperationA();
bool OperationB();
int main()
{
ArrayOperation arrayOp;
arrayOp.InitArray();
arrayOp.ShowArray();
return 0;
}
1.禁止复制
见条款6
2.对底层资源使用“引用计数法”
使用shared_ptr来计数
3.复制底部资源
4.转移底部资源的拥有权
条款15:在资源管理类中提供对原始资源的访问
Investment* createInvestment();
shared_ptr<Investment> pInv(createInvestment());
int func(const Investment* pi);
int days = func(pInv); //报错,func需要的是Investment*指针,但传入的却是shared_ptr<Investment>。
FontHandle getFont();
void releaseFont(FontHandle fh);
class Font
{
public:
explicit Font(FontHandle fh) :f(fh) {}
~Font() {releaseFont(f);}
//隐式转换函数
operator FontHandle() const {return f;}
private:
FontHandle f;
}
//进行隐式转换
Font f(getFont());
条款16:成对使用new/delete时要采取相同形式
条款17:以独立语句将newed对象置入智能指针
int num();
void processA(shared_ptr<A> pa,int num);
//现在调用processA函数
processA(new A, num());
由于shared_ptr构造函数是explicit的,无法进行隐式转换,因此需要写成
processA(shared_ptr<A>(new A), num());
但此时遇到的新问题就是执行顺序,该过程要做三件事:
//语句单独出来,不会造成泄漏
shared_ptr<A> pa(new A);
processA(pa, num());
总结:以独立语句将newed对象置入智能指针内,如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏