It's easy to write complicated things. Due to functional requirements, Vczh Library ++ 3.0 is very outrageous to me. To develop and maintain traversal, reduce mistakes caused by carelessness, and enhance unit testing, regression testing, and testing tools, record some development tips for the benefit of others. You are welcome to join us.
Today is the last article about memory. In the previous article, I explained why memset cannot be used for anything. The demo code contains a small bug, but I don't like to compile and run the demo code when I post it. So let's comment on any issues you have found. This makes it easier for later users to be misled. This is still about constructor and destructor. However, we will develop a smart pointer method to learn how reference counting can help manage resources, and when reference count is used incorrectly.
First, let's take a look at how smart pointers help us manage memory. Currently, there are many implementations of smart pointers. I assume this type is Ptr <T>. This is the same as the implementation of Vczh Library ++ 3.0.
1 class Base
2 {
3 public:
4 virtual ~ Base (){}
5 };
6
7 class Derived1: public Base
8 {
9 };
10
11 class Derived2: public Base
12 {
13 };
14
15 //---------------------------------------
16
17 List <Ptr <Base> objects;
18 objects. Add (new Derived1 );
19 objects. Add (new Derived2 );
20
21 List <Ptr <Base> objects2;
22 objects2.Add (objects [0]);
Of course, the List here is also implemented by Vczh Library ++ 3.0, but it is a concept like vector or C # List, so there is no need to explain it more. We can see a benefit of smart pointers. As long as there is no circular reference, no matter how you copy it, it will always be destructed. Another example shows how smart pointers handle type conversion:
1 Ptr <Derived1> d1 = new Derived1;
2 Ptr <Base> B = d1;
3 Ptr <Derived2> d2 = B. Cast <Derived2> ();
4 // d2 is empty, because B points to Derived1 rather than derived2.
This is just as if Derived1 * Can be implicitly converted to Base *, and when you use dynamic_cast <Derived2 *> (static_cast <Base *> (new Derived1), it will get 0. Smart pointers help us analyze objects, while also doing a good job of type conversion.
Now let's make the Ptr step by step. We need to know what the smart pointer is to implement, and then let's do it one by one. First, let's list a table:
1. Initialization is blank when no parameter is constructed
2. You can use a pointer to construct a pointer and delete the pointer when no smart pointer points to the pointer.
3. When the smart pointer is copied, the two smart pointers share the internal pointer.
4. A new smart pointer or bare pointer can be used to assign a new value to a smart pointer.
5. Implicit pointer type conversion is required. static_cast is not supported, while dynamic_cast is supported by Cast <T2> () member function.
6. If a bare pointer is directly used to create two smart pointers, it is expected that when the two smart pointers are parsed, the pointer will be deleted twice and thus crash.
7. Do not process circular references.
The last two points are actually the two most common cases of incorrect use of smart pointers. We implement one by one from 1 to 5. The first is 1. Smart pointers can be implicitly converted to bool, and internal T * can be obtained through operator-> *. When parameter construction is not used, convert it to false and get 0:
1 template <typename T>
2 class Ptr
3 {
4 private:
5 T * pointer;
6 int * counter;
7
8 void Increase ()
9 {
10 if (counter) ++ * counter;
11}
12
13 void Decrease ()
14 {
15 if (counter & -- * counter = 0)
16 {
17 delete counter;
18 delete pointer;
19 counter = 0;
20 pointer = 0;
21}
22}
23
24 public:
25 Ptr (): pointer (0), counter (0)
26 {
27}
28
29 ~ Ptr ()
30 {
31 Decrease ();
32}
33
34 operator bool () const
35 {
36 return counter! = 0;
37}
38
39 T * operator-> () const
40 {
41 return pointer;
42}
43 };
Here we have implemented constructor and destructor. The constructor initializes both the internal pointer and the reference counter pointer as null, while the Destructor deletes the reference count. The other two operators are easy to understand. Let's take a look at what the Increase function and Decrease function have done. The Increase function adds one reference count when the reference count exists. The Decrease function reduces the reference count by one when the reference count exists. If the reference count is changed to 0 when it is subtracted, the resources it owns are deleted.
Of course, the smart pointer cannot be used at this time. We must add a copy constructor for it, operator = operator overload, and pointer assignment. First, let's take a look at what should we add When pointer assignment is used:
1 Ptr (T * p): pointer (0), counter (0)
2 {
3 * this = p;
4}
5
6 Ptr <T> & operator = (T * p)
7 {
8 Decrease ();
9 if (p)
10 {
11 pointer = p;
12 counter = new int (1 );
13}
14 else
15 {
16 pointer = 0;
17 counter = 0;
18}
19 return * this;
20}
Here we just cut corners. If the constructor accepts the pointer, it will still be transferred to operator = for calling. When a smart pointer is assigned a value by a new pointer, we need to first reduce the reference count because the original pointer is no longer shared by this smart pointer. Then we will make a judgment. If 0 is returned, it will become null. If it is not 0, the pointer is obtained and the reference count is initialized to 1. So we can use it like this:
1 Ptr <Base> B = new Derived1;
2 Ptr <Derived2> d2 = new Derived2;
Let's Start copying them. The Essentials of copying is to first remove the previously held pointer and connect it to a new smart pointer. We know the number of non-null smart pointers and the sum of the total reference count, but the numbers allocated to each pointer are different:
1 Ptr (const Ptr <T> & p): pointer (p. pointer), counter (p. counter)
2 {
3 Increase ();
4}
5
6 Ptr <T> & operator = (const Ptr <T> & p)
7 {
8 if (this! = & P)
9 {
10 Decrease ();
11 pointer = p. pointer;
12 counter = p. counter;
13 Increase ();
14}
15 return * this;
16}
In the previous article, a friend pointed out that it is correct to assign a value to himself when performing operator = heavy load. When writing each class, especially when the class has its own resources, we need to pay attention to this issue. Of course, if you just copy a few objects and do not create a new delete or close a handle, it doesn't matter if you do not check it. Here we are very clear that when a new non-null smart pointer is added, the sum of reference counts will add one. When the result of modifying a non-null smart pointer is not null, the sum of reference counts remains unchanged. Of course this should be done, because we need to release all protected resources when all non-null smart pointers are destroyed.
Here, a smart pointer can basically be used, but it cannot process the parent class subclass. This is troublesome. A Ptr <Derived> actually does not have the permission to access the internal objects of Ptr <Base>. Therefore, we need to solve this problem through the youyuan class. Now let's add two new functions, from an arbitrary Ptr <C> to Ptr <T>, then ensure that only when C * Can be implicitly converted to T * Can the compilation pass:
1 template <X> friend class Ptr;
2
3 template <typename C>
4 Ptr (const Ptr <C> & p): pointer (p. pointer)