如果我们编写的类中没有方法,并且该类没有从另一个类继承,则编译器将自动向其添加六个方法。编译器可以自动生成的方法是:
- 默认构造函数:它等效于一个空的默认构造函数。默认构造函数是可以不带参数调用的构造函数。创建实例而不进行初始化时调用它。
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。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。