| Member function | typical form for class C: |
|---|---|
| Default constructor | C::C(); |
| Destructor | C::~C(); |
| Copy constructor | C::C (const C&); |
| Copy assignment | C& operator= (const C&); |
| Move constructor | C::C (C&&); |
| Move assignment | C& operator= (C&&); |
1
2
3
4
5
class Example {
public:
int total;
void accumulate (int x) { total += x; }
};
Example has a default constructor. Therefore, objects of this class can be constructed by simply declaring them without any arguments:1
Example ex;
1
2
3
4
5
6
class Example2 {
public:
int total;
Example2 (int initial_value) : total(initial_value) { };
void accumulate (int x) { total += x; };
};
int. Therefore the following object declaration would be correct:1
Example2 ex (100); // ok: calls constructor
1
Example2 ex; // not valid: no default constructor
// classes and default constructors
#include <iostream>
#include <string>
using namespace std;
class Example3 {
string data;
public:
Example3 (const string& str) : data(str) {}
Example3() {}
const string& content() const {return data;}
};
int main () {
Example3 foo;
Example3 bar ("Example");
cout << "bar's content: " << bar.content() << '\n';
return 0;
}
bar's content: Example
Example3 has a default constructor (i.e., a constructor without parameters) defined as an empty block:1
Example3() {}
Example3 to be constructed without arguments (like foo was declared in this example). Normally, a default constructor like this is implicitly defined for all classes that have no other constructors and thus no explicit definition is required. But in this case, Example3 has another constructor:1
Example3 (const string& str);
void. It also uses the class name as its own name, but preceded with a tilde sign (~):// destructors
#include <iostream>
#include <string>
using namespace std;
class Example4 {
string* ptr;
public:
// constructors:
Example4() : ptr(new string) {}
Example4 (const string& str) : ptr(new string(str)) {}
// destructor:
~Example4 () {delete ptr;}
// access content:
const string& content() const {return *ptr;}
};
int main () {
Example4 foo;
Example4 bar ("Example");
cout << "bar's content: " << bar.content() << '\n';
return 0;
}
bar's content: Example
Example4 allocates storage for a string. Storage that is later released by the destructor.foo and bar this happens at the end of function main.const qualified) and which can be invoked with a single argument of this type. For example, for a class MyClass, the copy constructor may have the following signature:1
MyClass::MyClass (const MyClass&);
1
2
3
4
class MyClass {
public:
int a, b; string c;
};
1
MyClass::MyClass(const MyClass& x) : a(x.a), b(x.b), c(x.c) {}
Example4 we defined above, because it contains pointers of which it handles its storage. For that class, performing a shallow copy means that the pointer value is copied, but not the content itself; This means that both objects (the copy and the original) would be sharing a single string object (they would both be pointing to the same object), and at some point (on destruction) both objects would try to delete the same block of memory, probably causing the program to crash on runtime. This can be solved by defining the following custom copy constructor that performs a deep copy:// copy constructor: deep copy
#include <iostream>
#include <string>
using namespace std;
class Example5 {
string* ptr;
public:
Example5 (const string& str) : ptr(new string(str)) {}
~Example5 () {delete ptr;}
// copy constructor:
Example5 (const Example5& x) : ptr(new string(x.content())) {}
// access content:
const string& content() const {return *ptr;}
};
int main () {
Example5 foo ("Example");
Example5 bar = foo;
cout << "bar's content: " << bar.content() << '\n';
return 0;
}
bar's content: Example
1
2
3
4
MyClass foo;
MyClass bar (foo); // object initialization: copy constructor called
MyClass baz = foo; // object initialization: copy constructor called
foo = bar; // object already initialized: copy assignment called
baz is initialized on construction using an equal sign, but this is not an assignment operation! (although it may look like one): The declaration of an object is not an assignment operation, it is just another of the syntaxes to call single-argument constructors.foo is an assignment operation. No object is being declared here, but an operation is being performed on an existing object; foo.operator= which takes a value or reference of the class itself as parameter. The return value is generally a reference to *this (although this is not required). For example, for a class MyClass, the copy assignment may have the following signature:1
MyClass& operator= (const MyClass&);
Example5. In this case, not only the class incurs the risk of deleting the pointed object twice, but the assignment creates memory leaks by not deleting the object pointed by the object before the assignment. These issues could be solved with a copy assignment that deletes the previous object and performs a deep copy:1
2
3
4
5
6
Example5& operator= (const Example5& x) {
delete ptr; // delete currently pointed string
ptr = new string (x.content()); // allocate space for new string, and copy
return *this;
}
string member is not constant, it could re-utilize the same string object:1
2
3
4
Example5& operator= (const Example5& x) {
*ptr = x.content();
return *this;
}
1
2
3
4
5
6
MyClass fn(); // function returning a MyClass object
MyClass foo; // default constructor
MyClass bar = foo; // copy constructor
MyClass baz = fn(); // move constructor
foo = bar; // copy assignment
baz = MyClass(); // move assignment
fn and the value constructed with MyClass are unnamed temporaries. In these cases, there is no need to make a copy, because the unnamed object is very short-lived and can be acquired by the other object when this is a more efficient operation.1
2
MyClass (MyClass&&); // move-constructor
MyClass& operator= (MyClass&&); // move-assignment
&&). As a parameter, an rvalue reference matches arguments of temporaries of this type.// move constructor/assignment
#include <iostream>
#include <string>
using namespace std;
class Example6 {
string* ptr;
public:
Example6 (const string& str) : ptr(new string(str)) {}
~Example6 () {delete ptr;}
// move constructor
Example6 (Example6&& x) : ptr(x.ptr) {x.ptr=nullptr;}
// move assignment
Example6& operator= (Example6&& x) {
delete ptr;
ptr = x.ptr;
x.ptr=nullptr;
return *this;
}
// access content:
const string& content() const {return *ptr;}
// addition:
Example6 operator+(const Example6& rhs) {
return Example6(content()+rhs.content());
}
};
int main () {
Example6 foo ("Exam");
Example6 bar = Example6("ple"); // move-construction
foo = foo + bar; // move-assignment
cout << "foo's content: " << foo.content() << '\n';
return 0;
}
foo's content: Example
| Member function | implicitly defined: | default definition: |
|---|---|---|
| Default constructor | if no other constructors | does nothing |
| Destructor | if no destructor | does nothing |
| Copy constructor | if no move constructor and no move assignment | copies all members |
| Copy assignment | if no move constructor and no move assignment | copies all members |
| Move constructor | if no destructor, no copy constructor and no copy nor move assignment | moves all members |
| Move assignment | if no destructor, no copy constructor and no copy nor move assignment | moves all members |
default and delete, respectively. The syntax is either one of:function_declaration = default; function_declaration = delete;
// default and delete implicit members
#include <iostream>
using namespace std;
class Rectangle {
int width, height;
public:
Rectangle (int x, int y) : width(x), height(y) {}
Rectangle() = default;
Rectangle (const Rectangle& other) = delete;
int area() {return width*height;}
};
int main () {
Rectangle foo;
Rectangle bar (10,20);
cout << "bar's area: " << bar.area() << '\n';
return 0;
}
bar's area: 200
Rectangle can be constructed either with two int arguments or be default-constructed (with no arguments). It cannot however be copy-constructed from another Rectangle object, because this function has been deleted. Therefore, assuming the objects of the last example, the following statement would not be valid:1
Rectangle baz (foo);
1
Rectangle::Rectangle (const Rectangle& other) = default;
1
Rectangle::Rectangle (const Rectangle& other) : width(other.width), height(other.height) {}
default does not define a member function equal to the default constructor (i.e., where default constructor means constructor with no parameters), but equal to the constructor that would be implicitly defined if not deleted.delete or default on the other special member functions they don't explicitly define.