如果我們編寫的類中沒有方法,並且該類沒有從另一個類繼承,則編譯器將自動向其添加六個方法。編譯器可以自動生成的方法是:
- 默認構造函數:它等效於一個空的默認構造函數。默認構造函數是可以不帶參數調用的構造函數。創建實例而不進行初始化時調用它。
class_name object_name;
考慮從具有默認構造函數的另一個類派生的類,或包含具有默認構造函數的另一個類對象的類。編譯器需要插入代碼來調用基類/嵌入式對象的默認構造函數。
#include <iostream> using namespace std; class Base { public: // compiler "declares" constructor }; class A { public: // User defined constructor A() { cout << "A Constructor" << endl; } // uninitialized int size; }; class B:public A { // compiler defines default constructor of B, and // inserts stub to call A constructor // compiler won't initialize any data of A }; class C:public A { public: C() { // User defined default constructor of C // Compiler inserts stub to call A's construtor cout << "C Constructor" << endl; // compiler won't initialize any data of A } }; class D { public: D() { // User defined default constructor of D // a - constructor to be called, compiler inserts // stub to call A constructor cout << "D Constructor" << endl; // compiler won't initialize any data of 'a' } private: A a; }; int main() { Base base; B b; C c; D d; return 0; }
輸出:
A Constructor A Constructor C Constructor A Constructor D Constructor
- 析構函數:它等效於一個空的析構函數。它調用超類析構函數和非原始類型的成員字段的析構函數。聲明析構函數的一般形式如下:
class_name::~class_name;
析構函數示例:
#include <iostream> using namespace std; class Example { private: int a, b; public: // Constructor Example() { cout << "Constructor is called" << endl; a = 10; b = 20; } // Destructor ~Example() { cout << "Destructor is called" << endl; } // Member function void print() { cout << "a = " << a << endl; cout << "b = " << b << endl; } }; int main() { // Object created Example obj; // Constructor Called // Member function called obj.print(); return 0; }
輸出:Constructor is called a = 10 b = 20 Destructor is called
- 複製構造函數:複製構造函數是一個構造函數,可以使用對類實例的引用作為參數來調用它。當實例的新副本需要由編譯器顯式或隱式初始化時(例如,將實例按值傳遞給函數或按值返回時),將調用此方法。它將使用構造函數參數的相應成員初始化每個實例成員。
下麵的程序演示了複製構造函數的使用,以便更好地理解。
- 複製分配運算符:它等效於將其參數的每個成員分配給此實例的對應成員的賦值運算符。請注意,這可能導致調用成員字段自己的副本分配運算符。
MyClass t1, t2; // copy constructor is called MyClass t3 = t1; // copy assignment operator is called t2 = t1;
複製構造函數和複製分配運算符的示例:
#include <iostream> using namespace std; class Line { public: // constructor Line(int len); // copy constructor Line(const Line& obj); void display(void); private: int* ptr; }; Line::Line(int len) { // allocate memory for the pointer; ptr = new int; *ptr = len; } // Copy Constructor Line::Line(const Line& obj) { cout << "Copy constructor allocating ptr." << endl; ptr = new int; // copy the value *ptr = *obj.ptr; } void Line::display() { cout << "Length of line:" << *ptr << endl; } // Main function for the program int main() { Line l1(10), l2(0); // Copy constructor called Line l3 = l1; // Copy assignment operator called l2 = l1; l1.display(); l2.display(); l3.display(); return 0; }
- 移動構造函數:move構造函數是一個構造函數,可以使用對類實例的右值引用作為參數來調用,通常是ClassName(const ClassName &&)。當從通常在初始化後被銷毀的臨時對象初始化新實例時調用它,例如,當從函數的值返回或像ClassName new_instance(std::move(existing_instance)中那樣通過顯式調用返回時。
- 移動分配運算符:C++ 11定義了兩個與移動語義相關的新函數:移動構造函數和移動賦值運算符。盡管複製構造函數和複製分配的目標是將一個對象複製到另一個對象,但是移動構造函數和移動分配的目標是將資源的所有權從一個對象轉移到另一個對象(這比製作副本要便宜得多)。
移動構造函數和移動分配運算符的示例:
#include <iostream> using namespace std; struct S { int* p; int n; // Move Constructor S(S&& other) :p{ exchange(other.p, nullptr) }, n{ exchange(other.n, 0) } { } // Move Assignment Operator S& operator=(S&& other) { // move p, leaving nullptr in other.p p = exchange(other.p, nullptr); // move n, leaving zero in other.n n = exchange(other.n, 0); return *this; } };
定義移動構造函數和移動分配工作等效於其副本副本。但是,盡管這些函數的複製風格采用const左值參考參數,但這些函數的移動風味使用非常量r-value參考參數。
注意:重要的是要意識到這些函數。問題不在於它們的存在,而是我們沒有被迫編寫自己的文件,並且在瑣碎的複製/初始化無法工作的情況下可能會忘記這樣做。
例如,以下程序先打印1,然後再打印-2。這是因為默認的複製分配運算符將原始指針複製到b2。
#include <bits/stdc++.h>
using namespace std;
class Buffer {
public:
Buffer(int size, int* buffer)
:size(size), buffer(buffer)
{
}
int size;
int* buffer;
};
int main()
{
const int kBufSize = 2;
int* buffer = new int[kBufSize]{ 1, 2 };
Buffer b1 = Buffer(kBufSize, buffer);
cout << b1.buffer[0] << endl;
Buffer b2 = b1;
b2.buffer[0] = -2;
cout << b2.buffer[0] << endl;
return 0;
}
輸出:
1 -2
使用默認的構造函數和析構函數時,類似的問題仍然存在,例如,不初始化字段,不回收內存等。總之,我們應該意識到複製和初始化對象是我們的責任。
注:本文由純淨天空篩選整理自rajnr6大神的英文原創作品 Default Methods in C++ with Examples。非經特殊聲明,原始代碼版權歸原作者所有,本譯文未經允許或授權,請勿轉載或複製。