C + + learning notes - smart pointer, type conversion

Note: the coding tool is CLion+Cygwin64

catalogue

Smart pointer

shared_ptr

Reference counter

Circular reference

weak_ptr

unique_ptr

Copywriting smart pointer

Type conversion

const_cast

static_cast

dynamic_cast

reinterpret_cast

Smart pointer

        Smart pointers can be used to automatically recycle objects created in new mode. You need to import the memory header file before using it.

shared_ptr

#include <iostream>

using namespace std;

#include <memory>
class Test{
public:
    ~Test(){
        cout << "Test Destructor" << endl;
    }
};
int main(){
    Test * test = new Test;
    shared_ptr<Test> ptr(test);
    return 0;
}

Output:

Test Destructor

        You can see that in the above code, the delete function is not called to recycle the object, and the destructor of the object is also called.        

Reference counter

         There will be an object reference counter inside the smart pointer, which records the number of times the object is referenced by the smart pointer.

#include <iostream>

using namespace std;

#include <memory>
class Test{
public:
    ~Test(){
        cout << "Test Destructor" << endl;
    }
};
int main(){
    Test * test = new Test;
    shared_ptr<Test> ptr(test);
    cout << "The reference count value is:" << ptr.use_count() << endl;
    shared_ptr<Test> ptr2 = ptr;
    cout << "The reference count value is:" << ptr.use_count() << endl;
    cout << "The reference count value is:" << ptr2.use_count() << endl;
    return 0;
}

Output:

The reference count value is: 1
 The reference count value is: 2
 The reference count value is: 2
Test Destructor

Circular reference

         shared_ptr type smart pointer may cause circular reference, so the destructor of the object cannot be called.

#include <iostream>

using namespace std;

#include <memory>
class B;
class A{
public:
    shared_ptr<B> ptr;

    ~A(){
        cout << "A Destructor" << endl;
    }
};

class B{
public:
    shared_ptr<A> ptr;

    ~B(){
        cout << "B Destructor" << endl;
    }
};


int main(){
    A * a = new A;
    B * b = new B;
    shared_ptr<A> ptrA(a);
    shared_ptr<B> ptrB(b);
    a->ptr = ptrB;
    b->ptr = ptrA;

    cout << "A The reference count for is:" << ptrA.use_count() << endl;
    cout << "B The reference count for is:" << ptrB.use_count() << endl;
    return 0;
}

Output:

A The reference count for is: 2
B The reference count for is: 2

        You can see that the print information in the destructors of A and B is not output, so the destructors of A and B are not called. This is because both A and B are created through new. Although ptrA and ptrB are recycled after the main function is executed, and the reference counter is reduced by 1, the mutual references in A and B still exist, the reference count is not 0, and the delete function is not called in the smart pointer to release A and B.

        · To solve the problem of circular references, you can use another smart pointer, weak_ptr.

weak_ptr

        weak_ptr type smart pointer will not increase the reference count of the object.

#include <iostream>

using namespace std;

#include <memory>
class B;
class A{
public:
    weak_ptr<B> ptr;

    ~A(){
        cout << "A Destructor" << endl;
    }
};

class B{
public:
    weak_ptr<A> ptr;

    ~B(){
        cout << "B Destructor" << endl;
    }
};


int main(){
    A * a = new A;
    B * b = new B;
    shared_ptr<A> ptrA(a);
    shared_ptr<B> ptrB(b);
    a->ptr = ptrB;
    b->ptr = ptrA;

    cout << "A The reference count for is:" << ptrA.use_count() << endl;
    cout << "B The reference count for is:" << ptrB.use_count() << endl;
    return 0;
}

Output:

A The reference count for is: 1
B The reference count for is: 1
B Destructor
A Destructor

unique_ptr

        unique_ptr does not allow the assignment of type pointers of the same type.

        unique_ptr has no reference count.

#include <iostream>

using namespace std;

#include <memory>

class Test{
public:
    ~Test(){
        cout << "Test Destructor" << endl;
    }
};

int main(){
    Test * test = new Test;
    unique_ptr<Test> ptr(test);
//    unique_ ptr<Test> ptr2 = ptr; //  Compilation failed
    return 0;
}

Output:

Test Destructor

Copywriting smart pointer

#include <iostream>

using namespace std;

template <typename T>
class Ptr{
private:
    T * object;
    int * count;
public:
    Ptr(){
        object = NULL;
        count = new int(1);
    }

    Ptr(T * t){
        object = t;
        count = new int(1);
    }

    ~Ptr(){
        if(--(*count) == 0){
            if(object)
            {
                delete object;
            }
            delete count;
            object = NULL;
            count = 0;
        }
    }

    // When a new smart pointer object is initialized with a smart pointer object, the copy constructor is called
    Ptr(const Ptr<T> & ptr){
        cout << "Called Ptr Copy constructor for" << endl;
        ++(*(ptr.count));
        // If the smart pointer references another object before, it needs to be released first
        if(--(*count) == 0){
            if(object){
                delete object;
            }
            delete count;
        }
        object = ptr.object;
        count = ptr.count;
    }

    // It is used when initializing an object with the default constructor and assigning another object to this object=
    // So overload the = operator
    Ptr & operator=(const Ptr & ptr){
        cout << "Overload called=operator functions " << endl;
        ++(*(ptr.count));
        // If the smart pointer references another object before, it needs to be released first
        if(--(*count) == 0){
            if(object){
                delete object;
            }
            delete count;
        }
        object = ptr.object;
        count = ptr.count;
        return *this;
    }

    int use_count(){
        return *(this->count);
    }
};

class Test{
public:
    ~Test(){
        cout << "Called Test Destructor" << endl;
    }
};

int main(){
    Test * test = new Test();
    Ptr<Test> ptr(test);
    cout << "ptr Reference count is:" << ptr.use_count() << endl;
    // Call copy constructor
    Ptr<Test> ptr2 = ptr;
    cout << endl << "ptr Reference count is:" << ptr.use_count() << endl;
    cout << "ptr2 Reference count is:" << ptr2.use_count() << endl;
    // Call overloaded = operator function
    Ptr<Test> ptr3;
    ptr3 = ptr;
    cout << endl << "ptr Reference count is:" << ptr.use_count() << endl;
    cout << "ptr2 Reference count is:" << ptr2.use_count() << endl;
    cout << "ptr3 Reference count is:" << ptr3.use_count() << endl;
    return 0;
}

Output:

ptr Reference count is: 1
 Called Ptr Copy constructor for

ptr Reference count: 2
ptr2 Reference count: 2
 Overload called=operator functions 

ptr Reference count: 3
ptr2 Reference count: 3
ptr3 Reference count: 3
 Called Test Destructor

Type conversion

const_cast

        Constant conversion, you can convert a constant pointer to a normal pointer, so you can modify the value of the memory address pointed to by the pointer.

#include <iostream>

using namespace std;

class Test{
public:
    string name = "default";
};

int main(){
    const Test* test = new Test;
    cout << "before: name = " << test->name << endl;
//    test->name = "update";//  Compilation failed
    Test * ntest = const_cast<Test*>(test);
    ntest->name = "update";
    cout << "after: name = " << test->name << endl;
    if(test){
        delete test;
        test = NULL;
    }
    return 0;
}

Output:

before: name = default
after: name = update

static_cast

        Static conversion is mainly to convert pointer types. For example, you can convert void * to int *, double * and so on, which is used in the previous thread.

        Static conversion can also convert objects of a parent type to subtypes.

        The static conversion calling function looks at the variable type on the left of the equal sign, which is determined by the compiler.

#include <iostream>

using namespace std;

class Base{
public:
    void show(){
        cout << "Base show" << endl;
    }
};

class Sub: public Base{
public:
    void show(){
        cout << "Sub show" << endl;
    }
};

int main(){
    int number = 9999;
    void * vp = &number;
    int * ip = static_cast<int*>(vp);
    cout << "*ip = " << *ip << endl;

    Base * base = new Base;
    // Static conversion can also convert objects of a parent type to subtypes.
    Sub * sub = static_cast<Sub*>(base);
    base->show();
    // The static conversion calling function looks at the variable type on the left of the equal sign, which is determined by the compiler.
    sub->show();
    if(base){
        // delete whoever comes out of new
        delete base;
        base = NULL;
    }
    return 0;
}

Output:

*ip = 9999
Base show
Sub show

dynamic_cast

        The dynamic transformation runtime knows which function to call.

        Dynamic conversion cannot convert a parent type object to a child type object.

        If the dynamic conversion returns NULL, the conversion fails.

        In dynamic conversion, the function of the parent class must be declared as a virtual function.

#include <iostream>

using namespace std;

class Base{
public:
    virtual void show(){
        cout << "Base show" << endl;
    }
};

class Sub: public Base{
public:
    void show(){
        cout << "Sub show" << endl;
    }
};

int main(){
    Base * base = new Base;
    // Dynamic conversion cannot convert an object of a parent type to a child type.
    Sub * sub = dynamic_cast<Sub*>(base);
    if(sub){
        cout << "Base->Sub Conversion succeeded" << endl;
        sub->show();
    }else{
        cout << "Base->Sub switch views" << endl;
    }

    Sub * sub2 = new Sub;
    Base * base2 = dynamic_cast<Base*>(sub2);
    if(base2)
    {
        cout << "Sub->Base Conversion succeeded" << endl;
        base2->show();
    }else{
        cout << "Sub->Base switch views" << endl;
    }

    if(base){
        // delete whoever comes out of new
        delete base;
        base = NULL;
    }
    if(sub2){
        // delete whoever comes out of new
        delete base;
        base = NULL;
    }
    return 0;
}

Output:

Base->Sub switch views
Sub->Base Conversion succeeded
Sub show

reinterpret_cast

        It has the functions of coercion and static conversion. At the same time, it can also convert objects into values and values into objects.

        It can be used to transfer object addresses between Java and C + +. Reinterpret is used in both Handler and Binder in Android source code_ Cast, C + + converts the object into a long value and passes it to Java. When C + + needs the object, Java passes the long value to C + +.

#include <iostream>

using namespace std;

class Test{
public:
    void show(){
        cout << "test reinterpret_cast" << endl;
    }
};
int main(){
    Test * test = new Test;
    long paddr = reinterpret_cast<long>(test);
    cout << "paddr = " << paddr << endl;
    Test * reTest = reinterpret_cast<Test*>(paddr);
    reTest->show();
    printf("paddr = %p\n", paddr);
    printf("test The stored address values are:%p\n", test);
    printf("reTest The stored address values are:%p\n", reTest);

    if(test){
        delete test;
        test = NULL;
    }
    return 0;
}

Output:

paddr = 34360045024
test reinterpret_cast
paddr = 0x80004ade0
test The stored address value is: 0 x80004ade0
reTest The stored address value is: 0 x80004ade0

Keywords: C++

Added by jobe1 on Wed, 01 Dec 2021 20:24:48 +0200