缺省構造函數

缺省構造函數C++及其他一些面向對象程式語言中,對象的不需要參數即可調用的構造函數

概述

對象生成時如果沒有顯式地調用構造函數,則缺省構造函數會被自動調用。C++標準規定,如果構造函數沒有參數(nullary),或者構造函數的所有參數都有缺省值(default value),都算作缺省構造函數。[1]一個只能有一個缺省構造函數。

例如,顯式定義、使用「缺省構造函數」:

class MyClass
{
  public:
    MyClass();             // constructor declared

  private:
    int x;
};

MyClass :: MyClass()       // constructor defined   
{
    x = 100;
}

int main()
{
    MyClass object;        // object created 
}                          // => default constructor called automatically

下述例子是動態生成對象時,顯示調用了缺省構造函數:

int main()
{
    MyClass * pointer = new MyClass();  // object created 
}                                       // => default constructor called automatically
    MyClass (int i = 0) {}

C++中,缺省構造函數會在下列情形被自動調用:

  • 對象被定義時無參數,例如:MyClass x;; 或動態分配對象時無參數列表,例如:new MyClassnew MyClass(); 缺省構造函數用於初始化對象。
  • 對象數組被定義時,例如:MyClass x[10];; 或被動態分配時,例如:new MyClass [10];缺省構造函數初始化數組的每個對象。
  • 當派生類在其初始化列表(initializer list)中沒有顯式調用其基類對象的構造函數時,基類的缺省構造函數被自動調用。
  • 當一個類的構造函數的初始化列表中沒有顯式調用其對象成員(object component或object-valued field)的構造函數時,對象成員的缺省構造函數被自動調用。
  • C++標準程序庫中,一些容器的填充值使用了值對象的缺省構造函數,如果這些值沒有顯式地給出。例如:vector<MyClass>(10);用10個元素初始化vector,這些元素用其缺省構造函數來初始化。

在上述這些情形中,如果被初始化地對象沒有缺省構造函數,則編譯時報錯。

如果一個類沒有顯式定義構造函數,編譯器將為其隱式地定義缺省構造函數(下文給出了例外情形)。隱式定義的缺省構造函數的函數體為空。例如:

class MyClass
{
    int x;                 // no constructor          
};                         // => the compiler produces an (implicit) default constructor
int main()
{
    MyClass object;        // no error: the (implicit) default constructor is called
}

如果類顯式地定義了一些構造函數,但不是缺省構造函數,編譯器則不會為該類隱式定義缺省構造函數。該類就沒有缺省構造函數了。也就是說,並不是所有類都有缺省構造函數。這是很多常見錯誤的原因。例如:

class MyClass 
{
  public:
    MyClass (int y);         // a constructor     

  private:
    int x;
};
MyClass :: MyClass (int y)
{
    x = y;
}
int main()
{
    MyClass object(100);     // constructor called
    MyClass *pointer;        // for declaration do not need to know about existing constructors
    pointer = new MyClass(); // error: no default constructor    
    return 0;
}

如果類的基類沒有缺省構造函數,那麼編譯器也不會為該類隱式地定義缺省構造函數。因為該類即使隱式地定義缺省構造函數,也無法初始化其基類。例如:

class A
{
public:
	explicit A(int){}
};

class B: public A
{
};

int main()
{
	B b1;         //编译报错
}

如果類的基類的缺省構造函數為私有,那麼編譯器會為該類隱式地定義缺省構造函數,但編譯報錯「cannot access private member declared in class 基類名字」。例如:

class A
{
   A(){};
public:
	
};

class B: public A
{
};

int main()
{
	B b1;         //编译报错
}

對於union數據類型,如果它包含了一個成員有非缺省的構造函數,那麼這個union類型的構造函數必須顯式調用這個成員類型的構造函數。例如:

struct bar {
	bar() {};
	bar(int) {};
};
union foo { 
	int i;
	float j;
	bar b;
        // foo(): b{ } { };    //只有增加这行,才能成功地构造一个foo类型的对象
};
foo vvv;         // syntax error: attempting to reference a deleted function

進階概念

class-name () = default;稱作「顯式地被缺省」(explicitly-defaulted)。

class-name () = delete;稱作「缺省構造函數被刪除」(The default constructor is deleted.)。

平凡的默認構造函數(Trivial default constructor)是不執行任何操作的構造函數。 所有與C語言兼容的數據類型(POD 類型)都是平凡的默認可構造。如果滿足以下所有條件,則類T的默認構造函數是平凡的(即不執行任何操作):

  • 構造函數不是用戶提供的(即,在其第一個聲明中隱式定義或默認)。
  • T 沒有虛成員函數。
  • T 沒有虛基類。
  • T 沒有帶有缺省的初始值設定項的非靜態成員(自 C++11 起)
  • T 的每個直接基類都有一個平凡的默認構造函數。
  • 類類型(或其數組)的每個非靜態成員都有一個平凡的默認構造函數。

隱式聲明的默認構造函數:如果沒有為類類型提供任何類型的用戶聲明的構造函數,則編譯器總是將默認構造函數聲明為其類的內聯公共成員。如果存在一些用戶聲明的構造函數,用戶仍然可以強制編譯器使用關鍵字 default自動生成默認構造函數。(自 C++11 起)

隱式定義的默認構造函數:如果隱式聲明或「顯式地被缺省」的缺省構造函數未被刪除,則由編譯器定義(即生成函數體並編譯),與具有空主體和空初始值列表的用戶定義構造函數具有相同的效果。也就是說,它調用該類的基類和非靜態成員的默認構造函數。 如果存在一些用戶定義的構造函數,用戶仍然可以強制編譯器自動生成默認構造函數,否則將使用關鍵字 default 隱式聲明該默認構造函數。

被刪除的缺省構造函數(Deleted default constructor),如果滿足以下任一條件,則類T的隱式聲明或顯式缺省的默認構造函數為已刪除(C++11起):

  • T 是一個union,其所有變體成員都是const限定類型(或是其多維數組)。
  • T 是一個非union的類,且所有匿名聯合成員都是const限定類型(或其多維數組)。
  • T 有一個引用類型的非靜態數據成員,沒有缺省初始化值(C++11 起)。
  • T 具有const限定類型的非變體、非靜態、非const缺省可構造的數據成員(或其多維數組),沒有缺省初始化值 (C++11 起)。
  • T 具有類類型為M(或其多維數組)的潛在可構造子對象,使得M有一個析構函數,該析構函數已被刪除或無法訪問缺省構造函數

如果不存在用戶定義的構造函數並且隱式聲明的缺省構造函數並不平凡,則用戶仍然可以使用關鍵字delete禁止編譯器自動生成隱式定義的缺省構造函數。

 
struct F
{
    int& ref; // reference member
    const int c; // const member
    // F::F() is implicitly defined as deleted
};

參考文獻

  1. ^ C++ standard, ISO/IEC 14882:1998, 12.1.5