Chapter 05 Smart Pointers and Memory Management

    In traditional C++, “remembering” to manually release resources is not always a best practice. Because we are likely to forget to release resources and lead to leakage. So the usual practice is that for an object, we apply for space when constructor, and free space when the destructor (called when leaving the scope). That is, we often say that the RAII resource acquisition is the initialization technology.

    There are exceptions to everything, we always have the need to allocate objects on free storage. In traditional C++ we have to use new and delete to “remember” to release resources. C++11 introduces the concept of smart pointers, using the idea of ​​reference counting, so that programmers no longer need to care about manually releasing memory. These smart pointers include std::shared_ptr/std::unique_ptr/std::weak_ptr, which need to include the header file <memory>.

    5.2 std::shared_ptr

    std::shared_ptr is a smart pointer that records how many shared_ptr points to an object, eliminating the display call delete, which automatically deletes the object when the reference count becomes zero.

    But not enough, because using std::shared_ptr still needs to be called with new, which makes the code a certain degree of asymmetry.

    std::make_shared can be used to eliminate the explicit use of new, so std::make_shared will allocate the objects in the generated parameters. And return the std::shared_ptr pointer of this object type. For example:

    1. auto pointer = std::make_shared<int>(10);
    2. auto pointer2 = pointer; // reference count+1
    3. auto pointer3 = pointer; // reference count+1
    4. int *p = pointer.get(); // no increase of reference count
    5. std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
    6. std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
    7. std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3
    8. pointer2.reset();
    9. std::cout << "reset pointer2:" << std::endl;
    10. std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
    11. std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
    12. pointer3.reset();
    13. std::cout << "reset pointer3:" << std::endl;
    14. std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
    15. std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
    16. std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 0, pointer3 has reset

    std::unique_ptr is an exclusive smart pointer that prohibits other smart pointers from sharing the same object, thus keeping the code safe:

    make_unique is not complicated. C++11 does not provide std::make_unique, which can be implemented by itself:

    1. template<typename T, typename ...Args>
    2. std::unique_ptr<T> make_unique( Args&& ...args ) {
    3. return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
    4. }

    As for why it wasn’t provided, Herb Sutter, chairman of the C++ Standards Committee, mentioned in his that it was because they were forgotten.

    Since it is monopolized, in other words, it cannot be copied. However, we can use std::move to transfer it to other unique_ptr, for example:

    5.4 std::weak_ptr

    If you think about std::shared_ptr carefully, you will still find that there is still a problem that resources cannot be released. Look at the following example:

    1. #include <iostream>
    2. #include <memory>
    3. class A;
    4. class B;
    5. class A {
    6. public:
    7. ~A() {
    8. }
    9. };
    10. class B {
    11. public:
    12. std::shared_ptr<A> pointer;
    13. ~B() {
    14. std::cout << "B was destroied" << std::endl;
    15. }
    16. };
    17. int main() {
    18. std::shared_ptr<A> a = std::make_shared<A>();
    19. std::shared_ptr<B> b = std::make_shared<B>();
    20. a->pointer = b;
    21. b->pointer = a;
    22. return 0;

    The result is that A and B will not be destroyed. This is because the pointer inside a, b also references a, b, which makes the reference count of a, b become 2, leaving the scope. When the a, b smart pointer is destructed, it can only cause the reference count of this area to be decremented by one. This causes the memory area reference count pointed to by the a, b object to be non-zero, but the external has no The way to find this area, it also caused a memory leak, as shown in Figure 5.1:

    The solution to this problem is to use the weak reference pointer std::weak_ptr, which is a weak reference (compared to std::shared_ptr is a strong reference). A weak reference does not cause an increase in the reference count. When a weak reference is used, the final release process is shown in Figure 5.2:

    In the above figure, only B is left in the last step, and B does not have any smart pointers to reference it, so this memory resource will also be released.

    std::weak_ptr has no * operator and -> operator, so it can’t operate on resources. Its only function is to check if std::shared_ptr exists, its expired() The method can return true when the resource is not released, otherwise it returns false.

    The technology of smart pointers is not novel. It is a common technology in many languages. Modern C++ introduces this technology, which eliminates the abuse of new/delete to a certain extent. It is a more mature technology. Programming paradigm.

    Table of Content | | Next Chapter: Regular Expression

    Further Readings