Smart Pointers And Constructor
Constructor
There are four kinds of constructor in C++, default constructor, copy constructor, move constructor and delegate constructor. To understand these constructors, a better way is to think as instance fall into a constructor rather than call a constructor. Consider we have such a simple class:
class test{
int* number;
}
Default constructor
In this case, the constructor takes properties as input, and assign them to member properties inside the class:
test(int number){
this->number = new int(number);
}
Copy constructor
When the constructor takes a left value instance as input (the concept of left value and right value won’t be introduce here), all initialization that provide a left value instance will fall into this kind of constructor. Actually, “copy constructor” isn’t birth as copy constructor, it is because by definition, left value is stored in a certain memory address and has name, we don’t want to modify it too much when we use it as input, as we might want to use it somewhere after this. So we basically want to make the new instance looks like the given instance, but actually have no relation between them, as for how to implement the constructor is depends on our demands:
test(test& instance){
this->number = new int(*(instance.number)); // so if we change the value in this->number, it doesn't affect the value in instance.number
}
Move constructor
As the complement of copy constructor, when the constructor takes a right value instance as input, it is called “move constructor”, because a right value will be discard after the current row of code, we usually just takes what it have directly rather then make a copy of it, because it will save time for copying. We usually use std::move()
in this case, but be aware, if we don’t do any thing in the constructor, this function won’t change any thing, that’s why I always emphasize that its our giving meaning to constructor to make them as “copy constructor"or “move constructor”, but not themselves have meaning.
test(test&& instance){
this->number = instance.number;
instance.number = nullptr;
// delete instance if necessary, you don't have to worry if you are using RAII
}
Delegate constructor
It is just call other constructor recursively, to save some time to write extra code.
Smart Pointer
Smart pointer is a feature which was introduced in C++11, it is based on the idea of RAII to solve memory leak. The general idea of smart pointer is for the piece of memory we point to, we also store a integer, use count, to count how many pointer we have currently pointing to this memory, if no pointer is pointing to this memory, we just release it to prevent memory leak.
Share Pointer
Share pointer allow multiple pointer pointing at the same memory, and it will increase use count by 1. The basic data structure of a share pointer is:
template<class T>
class Sptr{
private:
int* useCount;
T* pointer;
};
We want useCount
to be a pointer because it will be shared across all share pointer, put it in another way, when a new pointer is point to the same memory, we just need to increase useCount
in any of the pointer (usually in the new pointer), and the useCount
of all pointer that pointing at this memory will be increased.
So in the basic constructor we do two things, initialize useCount
and point to the memory:
Sptr(T* ptr = nullptr):pointer(ptr){
if(ptr)
useCount = new int(1);
else
useCount = new int(0);
}
Except this constructor, we still have two other ways for initialization:
-
Using
=
, we assign an exist pointer to a new one bySptr<int> a = b
, in this case, we need to insure they have the same type and increase/decreaseuseCount
properly:Sptr& operator=(const Sptr<T>& ptr){ if(ptr.pointer == this->pointer){ return *this; } if(this->pointer){ (*this->useCount)--; // the use count to the old memory should be decreased if(*(this->useCount) == 0) { delete this->useCount; delete this->pointer; } } this->useCount = ptr.useCount; this->pointer = ptr.pointer; if(this->pointer) (*this->useCount)++; return *this; }
-
Using constructor that takes instance as input:
Sptr(const Sptr<T> &ptr){ if(this!=&ptr){ printf("nope\n"); this->pointer = ptr.pointer; this->useCount = ptr.useCount; if(this->pointer){ (*this->useCount)++; } } }
There are also right value version of assign statement and move constructor, but since the general concept is introduced, we just save some time shall we?
Except constructor, destructor is also important for this pointer because we are working in RAII:
~Sptr(){
if(this->pointer){
(*this->useCount)--;
if(*(this->useCount) == 0)
{
delete this->useCount;
delete this->pointer;
}
}
}
At the end of the life time, the smart pointer will detach automatically, and the useCount
will be decreased.
Unique Pointer
It is only a special case to the share pointer, which allow only one pointer to the memory address, and we can transfer the ownership by only std:move()
.
Weak Pointer
It is a special case that doesn’t affect the useCount
, however it can not be used to access the memory by using ->
and *
, so normally it is used to monitor the useCount
.
Circular reference
In this problem, we have for share pointer as shown as arrow in the graph, when we reach the end of life of share_ptrA
and share_ptrB
, the mechanism of share pointer will check the useCount
of node A and node B, since there are internal share pointer pointing at each of them, they will not be deleted, which is not what we want.
If we have a pointer that does not affect the useCount
, we can use it as the internal pointer, and everything will be solved. However, as said before, we cannot access memory though weak pointer, but we can do it in another way: We can generate a share pointer by a weak pointer, so every time we need to access the memory, we generate a share pointer, after the access, we destroy the pointer manually.