GeekBand C++面向对象高级编程(上) Second Week

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:

  1. “带有default constructor“ 的member class object
  2. ”带有default constructor” 的Base Class
  3. “带有一个 Virtual Function” 的Class
  4. “带有一个 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函数的方式有二:

  1. 通过object调用
  2. 通过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 中相同的效果。

class template 和 function template

namespace

你可能感兴趣的:(GeekBand C++面向对象高级编程(上) Second Week)