Copy constructor
Complex c1{5, 2};
Complex c2{c1}; // Makes a copy!
double magnitude(Complex c);
The copy constructor is used when objs are passed by value.
c
is passed by value- A copy of
c
is made, and magnitude operats on the copy - The copy constructor is used
Complex(const Complex &c);
// beacuse it is already copied from passing by value step
Copy-assignment operator
Complex c1{5, 2};
Complex c2;
c2 = c1;
Complex & Complex::operator=(const Complex &c);
// return non-const reference to the LHS of the assignment,
// in order to support operator-chaning e.g. c3 = c2 = c1;
Allocating an Object on the Heap
When you need a large chunk of memry, or you need to create objects that live beyond the lifetime of a specific function call, you can allocate memory from the heap.
Complex *p = new Complex{3, 5};
cout << p.real() << ", " << p.imag(); // error
cout << p->real() << ", " << p->imag(); // OK!
delete p; // need to release it
// still contain the address but don't use it
Complex *p = new Complex[1000];
// init objs with default constructor
// if there is no default one, unavailable to allocate
p[0].real();
delete[] p;
// `new` => `delete`, or `new[]` => `delete[]`
Heap-Allocating Arrays of Primitives
No constructor or destructors so the values are uninitialized. If there is something at the memory location, there is a possibility that something is in the pointer eventually.
double *array = new double[numValues];
for (int i = 0; i < numValues; i++)
array[i] = 0;
delete[] array;
Managing Heap-Allocated Memory
Simple Solution: Don't heap-allocate memory at all. Use std::vector<T>
, std::array<T>
, std::string
, etc.
- Or, use Resource Acquisition Is Initialization (RAII) Pattern
- Heap-allocate memory in class constructor (and in a very few other places)
- Free memory in destructor
- The object manage memory for you -- abstraction/encapsulation
Array of Floats
class FloatArray {
int count;
float *elem;
public:
FloatArray(int n);
~FloatArray();
}
FloatArray::FloatArray(int n) {
count = n;
elems = new float[count];
for (int i = 0; i < count; i++)
elems[i] = 0;
}
FloatArray::~FloatArray() {
delete[] elems;
}
float getAverage() {
int numFloats;
cin >> numFloats;
FloatArray f{numFloats};
for (int i = 0; i < numFloats; i++) {
float value;
cin >> value;
f.set(i, value);
}
return f.average();
}
// after getAverage() called, destructor is called automatically
// so that heap memory allocated within f is freed
Custom Copy Constructor
FloatArray
stores elems on the heap. When it need to be copied, it will performs a shallow copy. Deep copy requires custom copy constructor.
FloatArray::FloatArray(const FloatArray &f) {
count = f.count;
elems = new float[count];
for (int i = 0; i < count; i++)
elems[i] = f.elems[i];
}
FlatArray fa1{n};
FlatArray fa2{fa1};
Custom copy-assignment operator
Assigning also needs a custom copy-assignment operator.
- The Rule of Three: If your class defines any of a dsestructor, a copy-constructor, and a copy-assignment operator, it probably needs to define all three. (
std::vector<T>
,std::array<T>
is simple way to deal from these issues) - The Rule of Zero: Write classes in such a way that you can rely on the default behavior of operations like the destructor, copy-constructor, copy-assignment, etc.
Make sure to release any dynamically-allocated resources, then allocate new res to receive the values from the RHS.
FloatArray & FloatArray::operator=(const FloatArray &f) {
delete[] elems; // Release old memory
count = f.count;
elems = new float[count]; // Allocate new memory
for (int i = 0; i < count; i++)
elems[i] = f.elems[i];
// Return non-const reference to myself
return *this;
}
Identify and handle self-assignment for avoiding waste.
FloatArray & FloatArray::operator(const FloatArray &f) {
// Detect and handle self-assignment
if (this == &f)
return *this;
delete[] elems; // Release old memory
count = f.count;
elems = new float[count];
for (int i = 0; i < count; i++)
elems[i] = f.elems[i];
// Return non-const refrence to myself
return *this;
}
bool
Type
bool operator==(const MyClass &c1, const MyClass &c2) {
//...
}
bool operator!=(const MyClass &c1, const MyClass &c2) {
return !(c1 == c2);
}
Inline Functions
If a function is short and simple, the compiler can simply replace the function-invocation with the function's body rather than spend function invocations.
// definition of functions as a part of the declaration
// no need to define the member functions in cpp file
class Complex {
double re, im;
public:
double real() const {
return re;
}
double imag() const {
return im;
}
}
cout << c.real();
// Compiles into:
cout << c.re;
The compiler will inline function when it is simple and straightforward only.
Defines a top-level function with inline
keyword (not a member-function in a class):
// in the header file
inline book operator==(const Complex &c1, const Complex &c2) {
return c1.real() == c2.real() && c1.imag() == c1.imag();
};
// without `inline`, it will return multiple definition errors