C++ Big Three
首先是默认构造函数(default constructor) 的概念,默认构造函数是没有参数或者全部参数都有默认值。不论这个构造函数是 user-defined 还是编译器 auto-generated。 C++ Annotated Reference Manual 中告诉我们 :" default constructor 是在需要的时候被编译器产生出来".
class Foo {public: int val; Foo *pnext};
void foo_bar()
{
Foo bar;
if(bar.val || bar.pnext)
{
// do something
}
}
在上面的程序中,正确的程序要求 Foo 有一个 default constructor,可以将它的两个 member 初始化为0. 但这个并不是C++语法中所说的 “需要的时候”。
这里的需要是指的编译器需要的时候,而不是程序的需要。C++ Standard 中提出 :" 如果没有任何 user-defined constructor,那么会有一个default constructor 被隐式(implicitly)声明出来, 一个被隐式声明出来的 default constructor将是一个 trivial constructor。 但是,即使是有编译器为类合成一个 default constructor, 那个constructor 也仅仅是为了执行编译器所需的动作,而不一定满足程序的需要。
在以下几种情况下,编译器会合成nontrivial default constructor:
- “带有default constructor“ 的member class object
- ”带有default constructor” 的Base Class
- “带有一个 Virtual Function” 的Class
- “带有一个 Virtual Base Class” 的Class
class String
{
public:
String(const char* cstr=0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char m_data;
}
copy constructor(拷贝构造函数)
在程序中会有一些时候以一个 object 的内容作为另外一个 class object 的初值。
如果class没有提供一个 explicit copy constructor时,当class object以相同class的另一个object作为初值时,其内部会以default memberwise initialization 初始化。 在一些情况下,这种初始化表现出的是 Bitwise Copy(位逐次拷贝)的行为。但在一些情况下,是编译器会自动合成copy constructor,但合成的copy constructor只是满足编译器的需求,并不能满足程序的需求。
在类中带有指针成员变量的时候,一般都需要user-define 的 copy ctor。
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
copy assignment operator(拷贝赋值函数)
copy op= 的语意 在默认的情况下也是一个 memberwise copy。当类有bitwise copy 语义时,编译器其实不会合成一个 copy op= 的函数。
只有在默认行为所导致的语意不安全或者不正确时,我们才需要一个user-define copy op= 的函数。
在类中带有指针成员变量的时候,一般都需要user-define copy assignment operator。
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
注意其中已一定要进行 self assignment的检查,防止自我赋值情况下的异常情况的出现。
destructor (析构函数)
如果没有user-defined destructor,只有在class 内含有member object(抑或是class 自己的 base class) 拥有 destructor的情况下,编译器才会自动合成一个,否则,destructor被视为不需要。
在还有指针成员变量的类中,一般需要user-defined的析够函数,以满足程序的正确运行,防止内存泄漏等。
object 的声明周期
object 可能存在于 stack和heap上。 Stack 是存在于某作用域的一块内存空间, stack上的变量的声明周期限于作用域内。 Heap 是由操作系统提供的一块 global的内存空间,程序可以动态的分配其中的若干块。
class Complex {...};
...
{
Complex c1(1,2); // c1 所占用的空间在stack上,c1 的生命周期在作用域结束后便结束,c1被称为 auto object
Complex* p = new Complex(3); // Complex(3) 是个临时对象,其所占用的空间是 new 从heap上分配的,并由p所指向,p本身是在stack上的
static Complex c2(1,2); // c2 是static object,其生命周期在作用域结束后仍然存在,直到整个程序结束
}
Complex c3(1,2); // c3 是global object, 其生命周期在整个程序结束之后才结束
int main()
{
}
heap object 需要注意 在指向这块内存的变量生命周期结束前,正确的调用析够函数,清理heap object,否则可能造成内存泄漏。如上面的 p , 如果在作用域结束前没有 delete p,则在作用域结束后,p变量不存在了,就没有机会去释放 p 所指向的 heap object了,会造成内存泄漏。
new 和 delete 的内存操作
new 是先分配内存,然后再调用 ctor。
Complex* pc = new Complex(1,2);
// 上面这句话会被编译器转化为
Complex* pc;
void* mem = operator new(sizeof(Complex)); // 分配内存
pc = static_cast(mem); // 转型
pc->Complex::Complex(1,2); // 调用构造函数
delete 是先调用 dtor, 然后再释放内存
Complex* pc = new Complex(1,2);
...
delete pc;
// 上面 delete pc 会被转化为
Complex::~Complex(pc); // 调用析够函数
operator delete(pc); // 释放内存
同时 array new 一定要搭配 array delete
String* p = new String[3];
...
delete[] p; // 调用3次 dtor
static
static可以用来修饰成员变量和成员函数。
static成员函数没有默认的this指针。
调用stati函数的方式有二:
- 通过object调用
- 通过class name调用
Sigleton 模式
通过将 ctor 放在 private 区域,可以实现Signleton的模式。
class A {
public:
static A& getInstance();
setup();
private:
A();
A(const A& rhs);
...
};
A& A::getInstatnce()
{
static A a;
return a;
}
C++11 中增加了delete关键字也可以达到将 copy constructor 放在private 中相同的效果。