Notes on C + + primer Chapter 12 dynamic memory

Time:2021-3-9
shared_ PTR and unique_ Operations supported by PTR explain
shared_ptrSP or unique_ ptr up Empty smart pointer, which can point to the object of type T
p Use p as a conditional judgment, true if P points to an object
*p Dereference p to get the object it points to
p->mem Equivalent to (* P). Mem
p.get() Returns the pointer saved in P. Be careful. If the smart pointer releases its object, the object the returned pointer points to disappears
Swap (P, q) or p.swap (q) Exchange pointers in P and Q
shared_ PTR unique operation explain
make_shared(args) Returns a shared_ PTR, pointing to a dynamically allocated object of type T. useargsInitialize this object
shared_ptrp(q) P is shared_ Copy of PTR Q; this operation increments the counter in Q. The pointer in Q must be converted to t*
p = q P and Q are both shared_ PTR, the saved pointers must be able to convert to each other. This operation will decrease the reference count of P and increase the reference count of Q. if the reference count of P becomes 0, the original memory managed by P will be released
p.unique() If P. use_ Count() is 1 and returns true; otherwise, it returns false
p.use_count() Returns the number of smart pointers to objects shared with p; may be slow, mainly for debugging
  1. shared_ptrMultiple pointers are allowed to point to the same object;unique_ptr“Exclusive” refers to the object. The standard library also defines aweak_ptrIt is a weak reference to shared_ Objects managed by PTR. All three types are defined in thememoryIn the header file.

  2. make_sharedAllocates an object in dynamic memory, initializes it, and returns theshared_ptr. Like the smart pointer,make_sharedAlso defined in header filememoryIn the middle.

    //Shared that points to an int with a value of 42_ ptr
    shared_ptr p3 = make_shared(42);
    //P4 points to a string with a value of "999999999"
    shared_ptr p4 = make_shared(10, '9');
    //P5 points to a value initialized int, that is, the value is 0
    shared_ptr p5 = make_shared();

    We usually use Auto to define an object to save make_ This method is relatively simple.

    //P6 points to a dynamically allocated empty vector
    auto p6 = make_shared>();
  3. shared_ PTR automatically destroys the managed objects_ PTR also releases the associated memory automatically. If you will share_ PTR is stored in a container, and then all the elements are no longer needed, but only some of them are used. Remember to erase those elements that are no longer needed.

  4. Programs use dynamic memory for one of three reasons:

    • The program doesn’t know how many objects it needs to use
    • The program does not know the exact type of the required object
    • Programs need to share data among multiple objects
  5. Example: defining the strblob class

    /* 
    *Function: we want different copies of blob objects to share the same elements.
    *That is, when we copy a blob, the original blob object and its copy should refer to the same underlying elements.
    */
    
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    class StrBlob
    {
    public:
        typedef vector::size_type size_type;
        //Constructors
        StrBlob() : data(make_shared>()) {}
        StrBlob(initializer_list il) : data(make_shared>(il)) {}
        size_type size() const { return data->size(); }
        bool empty() const { return data->empty(); }
        //Add and remove elements
        void push_back(const string &t) { data->push_back(t); }
        void pop_back()
        {
            check(0, "pop_back on empty StrBlob");
            data->pop_back();
        }
        //Element access: non const and const versions
        string &front()
        {
            //If vector is empty, check throws an exception
            check(0, "front on empty StrBlob");
            return data->front();
        }
        string &back()
        {
            check(0, "back on empty StrBlob");
            return data->back();
        }
        const string &front() const
        {
            check(0, "const front on empty StrBlob");
            return data->front();
        }
        const string &back() const
        {
            check(0, "const back on empty StrBlob");
            return data->back();
        }
    
    private:
        shared_ptr> data;
        //If the data [i] is illegal, an exception is thrown
        void check(size_type i, const string msg) const
        {
            if (i >= data->size()) 
                throw out_of_range(msg);
        }
    };
  6. By default, dynamically assigned objects areDefault initializationThis means that the values of objects of built-in or composite types will be undefined, while class type objects will be initialized with the default constructor.

  7. We can use the traditional construction method (using parentheses) or list initialization (using curly braces)

    Int * pi = new int (1024); // the value of the object pointed by pi is 1024
    String * PS = new string (10, '9'); // * PS is "999999999"
    //Vector has 10 elements with values from 0 to 9
  8. You can also initialize the value of dynamically allocated objects by following the type name with a pair of empty brackets

    String * PS1 = new string; // the default initialization is empty string
    String * PS = new string(); // the initialization value is empty string
    Int * Pi1 = New Int; // default initialization; * the value of Pi1 is undefined
    Int * Pi2 = new int(); // the value is initialized to 0; * Pi2 is 0
  9. If we provide an initializer surrounded by brackets, we can use Auto to infer the type of object we want to assign from this initializer. However, because the compiler infers the type to be allocated from the type of the initializer, only if theSingle initializerAuto can be used only when the

    Auto P1 = new auto (obj); // P points to an object of the same type as obj
                             //The object is initialized with obj
    Auto P2 = new auto {a, B, C}; // error: there can only be a single initializer in brackets
  10. It is legal to assign const objects with new:

    //Assign and initialize a const int
    const int *pci = new const int(1024);
    //Assign and initialize an empty string of const by default
     const string *pcs = new const string;
  11. Like any other const object, a dynamically allocated const object must be initialized. For a class type with a default constructor, its const dynamic object can be initialized implicitly, while other types of objects must be initialized explicitly. Since the allocated object is const, the pointer returned by new is a pointer to const.

  12. By default, if new cannot allocate the required memory space, it throws abad_allocIt’s abnormal.

  13. Positioning newExpressions allow us to pass additional arguments to new. In this case, we pass it a file namednothrowThe object of the. IfnothrowTo new, our intention is to tell it not to throw an exception. If this form of new cannot allocate the required memory, it returns a null pointer.bad_allocandnothrowAre defined in the header filenewIn the middle.

    //If allocation fails, new returns a null pointer
    Int * P1 = New Int; // if allocation fails, new throws STD:: bad_ alloc
    int *p2 = new (nothrow) int; //If allocation fails, new returns a null pointer
  14. The pointer passed to delete must point to dynamically allocated memory, or a null pointer.

  15. Although the value of a const object cannot be changed, it can be destroyed

    const int *pci = new const int(1024);
    Delete PCI; // correct: releases a const object
  16. Dynamic memory managed by built-in pointers (rather than smart pointers) will exist until it is explicitly released.

  17. Null pointer: a pointer to a memory that used to hold data objects but is no longer valid.

  18. Avoid the null pointer problem: free the memory associated with the pointer just before it leaves its scope. If we need to keep the pointer, we can use thedeleteAfter that will benullptrAssign a pointer so that it is clear that the pointer does not point to any object.It only provides limited protection

  19. You can initialize the smart pointer with the pointer returned by new

    shared_ ptr p1; // shared_ PTR can point to a double
    shared_ PTR P2 (New int (42)); // P2 points to an int with a value of 42
  20. The smart pointer constructor that accepts pointer arguments isexplicitYes. Therefore, we can not implicitly convert a built-in pointer into a smart pointer. We must use the direct initialization form to initialize a smart pointer

    shared_ PTR P1 = new int (1024); // error: direct initialization must be used
    shared_ PTR P2 (New int (1024)); // correct; direct initialization is used

    A returnshared_ptrCannot implicitly convert a normal pointer in its return statement:

    shared_ptr clone(int p)
    {
    	Return new int (P); // error: implicitly converted to shared_ ptr
    }

    We’ve got toshared_ptrExplicitly bind to a pointer you want to return:

    shared_ptr clone(int p)
    {
    	//Correct: explicitly create shared with int *_ ptr
    	return shared_ptr(new int(p));
    }
  21. By default, a normal pointer used to initialize the smart pointer must point to dynamic memory, because the smart pointer uses delete to release its associated object by default. We can bind the smart pointer to a pointer to other types of resources, but in order to do so, we must provide our own operation to replace delete.

Define and change shared_ Other methods of PTR explain
shared_ptr p(q) P manages the object to which the built-in pointer Q points; Q must pointnewAllocated memory, and can be converted toT*type
shared_ptr p(u) P fromunique_ptr uThere takes over ownership of the object; leave u blank
shared_ptr p(q, d) P takes over ownership of the object pointed to by the built-in pointer Q. Q must be converted toT*Type. P will use the callable object d insteaddelete
shared_ptr p(p2, d) P isshared_ptr p2The only difference is that P will be replaced by the callable object Ddelete
p. Reset () or P. reset (q) or P. reset (Q, d) If P is the only one that points to its objectshared_ptrresetThe object is released. If the optional parameter built-in pointer q is passed, P will point to Q, otherwise P will be set to null. If parameter D is also passed, D is called instead of DdeleteTo release Q
  1. Don’t mix normal and smart pointers. shared_ PTR can coordinate the deconstruction of objects, but this is limited to its own copy (also shared)_ PTR).

    //PTR is created and initialized when the function is called
    void process(shared_ptr ptr)
    {
        //Using PTR
    }// PTR leaves the scope and is destroyed
    
    int main()
    {
        shared_ PTR P (New int (42)); // reference count is 1
        Process (P); // copying P will increment its reference count; in process, the reference count is 2
        Int i = * p; // correct: the reference count value is 1
        
        Int * x (New int (1024)); // danger: X is an ordinary pointer, not an intelligent pointer
        Process (x); // error: cannot convert int * to a shared_ ptr
        process(shared_ PTR (x)); // legal, but memory will be released!
        Int j = * x; // undefined: X is an empty pointer!
        
        return 0;
    }

    It’s dangerous to use a built-in pointer to access the object that a smart pointer is responsible for, because we can’t know when the object will be destroyed.

  2. Get is used to pass the access permission of the pointer to the code. You can only use get if you are sure that the code will not delete the pointer. In particular, never use get to initialize or assign a value to another smart pointer.

  3. There are two possibilities for function exit: normal processing ends or an exception occurs. In either case, the local object will be destroyed. Therefore, if the smart pointer is used, even if the program block ends prematurely, the smart pointer class can ensure that the memory is released when it is no longer needed

    void f()
    {
    	shared_ PTR sp (New int (42)); // assign a new object
    	//This code throws an exception and is not caught in F
    }// shared at the end of the function_ PTR releases memory automatically
  4. If the built-in pointer is used to manage the memory, and an exception occurs after new before the corresponding delete, the memory will not be released.

  5. Intelligent pointer can provide safe and convenient management of dynamically allocated memory, but it is based on the premise of correct use. In order to use the smart pointer correctly, we must adhere to some basic norms

    • Multiple smart pointers are not initialized (or reset) with the same built-in pointer value.
    • Do not delete the pointer returned by get().
    • Do not use get() to initialize or reset another smart pointer.
    • If you use the pointer returned by get (), remember that when the last corresponding smart pointer is destroyed, your pointer becomes invalid.
    • If the resource you use smart pointer to manage is not new allocated memory, remember to pass it a delegator.
  6. A unique_ PTR “owns” the object it points to. And shared_ Different from PTR, there can only be one unique at a time_ PTR points to a given object. When unique_ When PTR is destroyed, the object it points to is also destroyed.

  7. And shared_ PTR is different from make_ The standard library function of shared returns a unique_ ptr。 When we define a unique_ PTR needs to be bound to a pointer returned by new. Similar to shared_ PTR, initialize unique_ PTR must be in the form of direct initialization

    unique_ PTR P1; // can point to the unique of a double_ ptr
    unique_ PTR P2 (New int (42)); // P2 points to an int with a value of 42

    Because of a unique_ ptrhaveThe object it points to, so unique_ PTR does not support normal copy or assignment operations

    unique_ptr p1(new string("Stogosaurus"));
    unique_ PTR P2 (P1); // error: unique_ PTR does not support copying
    unique_ptr p3;
    P3 = P2; // error: unique_ PTR does not support assignment
unique_ PTR operation explain
unique_ptrU1 or unique_ ptr u2 Empty unique_ PTR, which can point to an object of type T. U1 uses delete to release its pointer; U2 uses a callable object of type D to release its pointer
unique_ptr u(d) Empty unique_ PTR, pointing to the object of type T, and replacing delete with object D of type D
u = nullptr Release the object u points to and leave u empty
u.release() U gives up control of the pointer, returns the pointer, and sets u to null
u. Reset() or u.reset (q) or u.reset (nullptr) Release the object that u points to. If a built-in pointer q is provided, let u point to this object; otherwise, set u to null
  1. Although we can’t copy or assign unique_ PTR, but you can change the ownership of the pointer from a (non const) unique by calling release or reset_ PTR transfers to another unique:

    //Transfer ownership from P1 (pointing to string stogosaurus) to P2
    unique_ PTR P2 (P1. Release()); // release sets P1 to null
    unique_ptr p3(new string("Trex"));
    //Transfer ownership from P3 to P2
    P2. Reset (P3. Release()); // reset releases the memory originally pointed by P2
  2. Calling release cuts off unique_ The connection between PTR and the object it originally managed. The pointer returned by release is usually used to initialize or assign a value to another smart pointer. However, if we do not use another smart pointer to save the pointer returned by release, our program will be responsible for resource release

    P2. Release(); // error: P2 does not free memory, and we lost the pointer
    Auto P = P2. Release(); // correct, but we must remember to delete (P)
  3. Cannot copy unique_ There is one exception to the PTR rule: we can copy or assign a unique to be destroyed_ ptr。 The most common example is to return a unique from a function_ ptr:

    unique_ptr clone(int p)
    {
    	//Correct: create a unique from int *_ ptr
    	return unique_ptr(new int(p));
    }

    You can also return a copy of a local object

    unique_ptr clone(int p)
    {
    	unique_ptr ret(new int(p));
    	// ...
    	return ret;
    }
  4. Overload a unique_ The remover in PTR will affect unique_ PTR type and how to construct (or reset) objects of this type. Similar to the comparison operation for overloaded associated containers, we must use unique in angle brackets_ PTR points to the type and then provides the delegator type. Create or reset a unique of this kind_ PTR type object, you must provide a callable object of the specified type (remover)

    //P points to an object of type obj T and releases the obj t object with an object of type delt
    //It calls a delt type object named FCN
    unique_ptr p(new objT, fcn);
    Void f (destination & D / * other required parameters * /)
    {
    	Connection C = connect (& D); // open connection
    	//When p is destroyed, the connection will be closed
    	unique_ptr p(&c, end_connection);
    	//Using connections
    	//When f exits (even if it exits due to an exception), the connection will be closed correctly
    }
  5. weak_ PTR is an intelligent pointer that does not control the lifetime of the object it points to_ Objects managed by PTR. Put a weak_ PTR is bound to a shared_ PTR doesn’t change shared_ The reference count of PTR.

weak_ptr explain
weak_ptr w Empty weak_ PTR can point to an object of type T
weak_ptr w(sp) And shared_ PTR SP points to the weak of the same object_ ptr。 T must be convertible to the type pointed to by sp
w = p P can be a shared_ PTR or a weak_ ptr。 After assignment, W and P share objects
w.reset() Leave w blank
w.use_count() Shared with W_ The number of PTR
w.expired() If w.use_ Count() is 0, return true, otherwise return false
w.lock() If expired is true, an empty shared is returned_ Ptr; otherwise, it returns a shared value of the object pointing to W_ ptr
  1. We cannot use weak because the object may not exist_ PTR directly accesses the object, and lock must be called.

    auto p = make_shared(42);
    weak_ The reference count of PTR WP (P); // WP weakly shared p; P does not change
    
    if (shared_ ptr np =  wp.lock ()) // if NP is not empty, the condition holds
    {
    	//In if, NP and P share objects
    	// ...
    }

    Only when the lock call returns true will we enter the body of the if statement. In if, it is safe to use NP to access shared objects.

  2. Example: strblobptr

    /*
    *Function: add an accompanying pointer class for strblob
    */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    //Declare in advance, which is required by friend declaration in strblob
    class StrBlobPtr;
    
    class StrBlob
    {
        friend class StrBlobPtr;
    public:
        typedef vector::size_type size_type;
        StrBlob();
        StrBlob(initializer_list il);
        StrBlob(vector *p);
        size_type size() const { return data->size(); }
        bool empty() const { return data->empty(); }
        //Add and remove elements
        void push_back(const string &t) {data->push_back(t);}
        void pop_back();
        //Element access
        string& front();
        const string& front() const;
        string& back();
        const string& back() const ;
    
        //Interface provided to strblobptr
        Strblobptr begin(); // these two functions can only be defined after defining strblobptr
        StrBlobPtr end();
        //Const version
        StrBlobPtr begin() const;
        StrBlobPtr end() const;
    private:
        shared_ptr> data;
        //If the data [i] is illegal, an exception is thrown
        void check(size_type i, const std::string &msg) const;
    };
    
    inline StrBlob::StrBlob(): data(make_shared>()) { }
    inline StrBlob::StrBlob(initializer_list il) : data(make_shared>(il)) { }
    inline StrBlob::StrBlob(vector *p): data(p) { }
    
    inline void StrBlob::check(size_type i, const string &msg) const
    {
        if (i >= data->size())
        {
            throw out_of_range(msg);
        }
    }
    
    inline string& StrBlob::front()
    {
        //If vector is empty, check throws an exception
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    //Const versionfront
    inline const string& StrBlob::front() const
    {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    inline string& StrBlob::back()
    {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    //Const versionback
    inline const string& StrBlob::back() const
    {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    inline void StrBlob::pop_back()
    {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }
    
    //Strblobptr throws an exception when trying to access a nonexistent element
    class StrBlobPtr
    {
        friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
    public:
        StrBlobPtr(): curr(0) { }
        StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }
        StrBlobPtr(const StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }
    
        string& deref() const;
        string& deref(int off) const;
        Strblobptr & incr(); // prefix increment
        Strblobptr & decr(); // prefix decrement
    private:
        //If the check is successful, check returns a shared to vector_ ptr
        shared_ptr> check(size_t, const string&) const;
    
        //Save a weak_ PTR, which means that the underlying vector may be destroyed
        weak_ptr> wptr;
        size_ T curr; // the current position in the array
    };
    
    inline shared_ptr> StrBlobPtr::check(size_t i, const string &msg) const
    {
        auto ret =  wptr.lock (); // does vector still exist?
        if (!ret)
        {
           throw runtime_error("unbound StrBlobPtr");
        }
        if (i >= ret->size())
        {
           throw out_of_range(msg);
        }
        Return; // otherwise, returns the shared to the vector_ ptr
    }
    
    inline string& StrBlobPtr::deref() const
    {
        auto p = check(curr, "dereference past end");
        Return (* P) [curr]; // (* P) is the vector that the object points to
    }
    
    inline string& StrBlobPtr::deref(int off) const
    {
        auto p = check(curr + off, "dereference past end");
        Return (* P) [curr + off]; // (* P) is the vector that the object points to
    }
    
    //Prefix increment: returns the reference of the incremented object
    inline StrBlobPtr& StrBlobPtr::incr()
    {
        //If curr is already pointing to the end of the container, it cannot be incremented
        check(curr, "increment past end of StrBlobPtr");
        ++Curr; // advance current position
        return *this;
    }
    
    //Prefix decrement: returns the reference of the decremented object
    inline StrBlobPtr& StrBlobPtr::decr()
    {
        //If curr is already 0, decreasing it will produce an illegal subscript
        --Curr; // decrement current position
        check(-1, "decrement past begin of StrBlobPtr");
        return *this;
    }
    
    //The definition of begin and end members of strblob
    inline StrBlobPtr StrBlob::begin()
    {
        return StrBlobPtr(*this);
    }
    
    inline StrBlobPtr StrBlob::end()
    {
      	auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
    
    //Const version
    inline StrBlobPtr StrBlob::begin() const
    {
    	  return StrBlobPtr(*this);
    }
    
    inline StrBlobPtr StrBlob::end() const
    {
        auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
    
    //Comparison operation of strblobptr
    inline bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
    {
        auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
        //If the underlying vectors are the same
        if (l == r)
        {
        	  //Then both pointers are empty, or they are equal when they point to the same element
        	  return (!r || lhs.curr == rhs.curr);
        }
        else
        {
        	  Return false; // if it points to different vectors, it cannot be equal
        }
    }
    
    inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
    {
    	  return !eq(lhs, rhs);
    }
  3. In order for new to allocate an array of objects, we need to follow the type name with a square bracket to indicate the number of objects to be allocated. In the following example, new allocates the required number of objects and (assuming successful allocation) returns a pointer to the first object:

    //Call get_ Size determines how many ints are allocated
    int *pia = new int[get_ Size ()]; // PIA points to the first int

    The size in square brackets must be integer, but not constant.

  4. Assigning an array gives you a pointer to the element typeInstead of an array type object. Because the allocated memory is not of an array type, begin or end cannot be called on a dynamic array. You can’t use the range for statement to handle elements in a (so-called) dynamic array. It’s important to remember that dynamic arrays are not array types.

  5. By default, objects allocated by new, whether individually or in an array, are initialized by default. You can initialize the value of an element in an array by following the size with a pair of empty parentheses. We can also provide a curly bracket list of element initializers.

    Int * PIA = New Int [10]; // 10 uninitialized ints
    Int * pia2 = New Int [10] (); // 10 ints initialized to 0
    String * PSA = new string [10]; // 10 empty strings
    String * PSA2 = new string [10] (); // 10 empty strings
    
    //The 10 ints are initialized with the corresponding initializers in the list
    int *pia3 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    //Ten strings, the first four are initialized with the given initializer, and the rest are initialized with values
    string *ps3 = new string[10]{"a", "an", "the", string(3,'x')};
  6. If the number of initializers is less than the number of elements, the remaining elements are initialized with values. If the number of initializers is greater than the number of elements, the new expression fails and no memory is allocated. New will throw abad_array_new_lengthIt’s abnormal. similarbad_alloc, which is defined in the header file new.

  7. Although we use empty brackets to initialize the values of the elements in the array, we can’t give initializers in the brackets, which means that we can’t use Auto to allocate the array.

  8. It is legal to dynamically allocate an empty array. When we allocate an array of size 0 with new, new returns a legal non null pointer. This pointer is guaranteed to be different from any other pointer returned by new. We can use this pointer just like we use a trailing iterator. But this pointer doesn’t dereference — after all, it doesn’t point to any element.

    Char arr [0]; // error: cannot define an array of length 0
    Char * CP = new char [0]; // correct: but CP cannot be dereferenced
  9. In order to release the dynamic array, we use a special form of delete – adding a pair of empty square brackets before the pointer

    Delete p; // P must point to a dynamically allocated object or be empty
    Delete [] Pa; // PA must point to a dynamically allocated array or be empty
  10. The elements in an array are destroyed in reverse order, that is, the last element is destroyed first, then the penultimate, and so on.

  11. When we use a type alias to define an array type, we do not use [] in the new expression. Even so, square brackets must be used when releasing an array pointer.

    Typedef int ARRT [42]; // ARRT is the type alias of an array of 42 ints
    Int * P = new ARRT; // allocates an array of 42 ints; P points to the first element
    Delete [] p; // square brackets are required because we originally allocated an array
  12. If we forget square brackets when we delete an array pointer, or use square brackets when we delete a single object pointer, the compiler will probably not give a warning. Our program may behave abnormally during execution without any warning.

  13. The standard library provides a unique way to manage arrays allocated by new_ PTR version. In order to use a unique_ PTR manages dynamic arrays. We must follow the object type with a pair of empty square brackets

    //Up points to an array of 10 uninitialized ints
    unique_ptr up(new int[10]);
    up.release (); // automatically use delete [] to destroy its pointer

    Square brackets in type specifiers()Indicates that up points to an int array instead of an int. Since up points to an array, when up destroys the pointer it manages, it will automatically use delete.

Unique pointing to an array_ ptr explain
—— Unique pointing to an array_ PTR does not support member access operators (dot and arrow operators)
—— Other unique_ The PTR operation remains unchanged
unique_ptr u U can point to a dynamically allocated array. The element type of the array is t
unique_ptr u(p) U points to the dynamically allocated array pointed to by the built-in pointer P. P must be convertible to type T*
u[i] Returns the object at I in the array owned by U.U must point to an array
  1. Andunique_ptrDifferent,shared_ptrManagement of dynamic arrays is not directly supported. If you want to useshared_ptrTo manage a dynamic array, you must provide your own delegator:

    //To use shared_ PTR, must provide a delete
    shared_ptr sp(new int[10], [](int *p){ delete[] p; });
    sp.reset (); // use our lambda to release the array, which uses delete []

    If no delegator is provided, the code is undefined. By default,shared_ptrusedeleteDestroy the object it points to.

  2. shared_ptrThe subscript operator is not defined, and the smart pointer type does not support pointer arithmetic. Therefore, in order to access elements in an array, you must use thegetGet a built-in pointer and use it to access array elements.

    // shared_ PTR does not define subscript operator and does not support pointer arithmetic operation
    for (size_t i = 0; i != 10; ++i)
    	*( sp.get () + I) = I; // use get to get a built-in pointer
  3. newThere are some flexibility limitations, one of which is the combination of memory allocation and object construction. allied,deleteObject deconstruction and memory release are combined. More importantly, classes that do not have a default constructor cannot dynamically allocate arrays.

  4. Standard libraryallocatorClass is defined in the header filememoryIt helps us separate memory allocation from object construction.

Standard library allocator class and its algorithm explain
allocator a An allocator object named a is defined, which can allocate memory for objects of type T
a.allocate(n) Allocate a piece of original and unstructured memory to store n objects of type T
a.deallocate(p, n) Release the memory starting from the address in t * pointer P, which holds n objects of type T; P must be a pointer previously returned by allocate, and N must be the size required when p was created. Before calling deallocate, the user must call destroy on each object created in this memory
a.construct(p, args) P must be a pointer of type T * to a block of original memory;argsIs passed to a constructor of type T, which is used to construct an object in memory pointed to by P
a.destroy(p) P is a pointer of type T * and this algorithm performs a destructor on the object pointed to by P
Allocator alloc; // allocator object of string can be allocated
auto const p =  alloc.allocate (n) ; // allocate n uninitialized strings

Auto q = p; // Q points to the position after the last constructed element
alloc.construct (Q + +); // * q is an empty string
alloc.construct (Q + +, 10, 'C'); // * q is CCCC
alloc.construct (Q + + "Hi"); // * q is hi

Cout < * P < < endl; // correct: use the output operator of string
Cout < * q < < endl; // disaster: Q points to unstructured memory!

while (q != p)
    alloc.destroy (-- Q); // release the string we really construct

alloc.deallocate (P, n); // free memory
  1. In order to useallocateReturn the memory we have to useconstructConstruct objects. Using unstructured memory, its behavior is undefined.
  2. We can only evaluate the elements that are actually constructeddestroyOperation.
Allocator algorithm explain
—— These functions create elements at a given destination location, rather than having the system allocate memory to them
uninitialized_copy(b, e, b2) Copy elements from the input ranges indicated by iterators B and e to the unstructured original memory specified by iterator B2. The memory that B2 points to must be large enough to hold copies of the elements in the input sequence
uninitialized_copy_n(b, n, b2) Starting from the element pointed by iterator B, copy n elements to the memory starting from B2
uninitialized_fill(b, e, t) Create an object in the original memory range specified by iterators B and E, and the value of the object is a copy of T
uninitialized_fill_n(b, n, t) Create n objects from the memory address pointed to by iterator B. B must point to an unstructured raw memory large enough to hold a given number of objects
  1. similarcopyuninitialized_copyReturns the (incremented) destination iterator. So, onceuninitialized_copyThe call returns a pointer to the position after the last constructed element.

    //Allocate twice as much dynamic memory as the elements in VI
    auto p = alloc.allocate(vi.size() * 2);
    //Construct elements starting from P by copying the elements in VI
    auto q = uninitialized_copy(vi.begin(), vi.end(), p);
    //Initializes the remaining elements to 42
    uninitialized_fill_n(q, vi.size(), 42);
  2. If two classes conceptually “share” data, you can use theshared_ptrTo reflect the sharing relationship in the data structure.

  3. When we design a class, it is a very useful way to write a program to use the class before actually implementing members. In this way, we can see whether the class has the operations we need.

  4. Using standard library: text query program

    • my_TextQuery.cpp

    #include "my_TextQuery.h"
    #include "make_plural.h"
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using std::size_t;
    using std::shared_ptr;
    using std::istringstream;
    using std::string;
    using std::getline;
    using std::vector;
    using std::map;
    using std::set;
    using std::cerr;
    using std::cout;
    using std::cin;
    using std::ostream;
    using std::endl;
    using std::ifstream;
    using std::ispunct;
    using std::tolower;
    using std::strlen;
    using std::pair;
    
    // read the input file and build the map of lines to line numbers
    TextQuery::TextQuery(ifstream &is): file(new vector)
    {
        string text;
        while (getline(is, text)) {       // for each line in the file
    		file.push_back(text);        // remember this line of text
    		int n = file.size() - 1;     // the current line number
    		istringstream line(text);     // separate the line into words
    		string word;
    		while (line >> word) {        // for each word in that line
                word = cleanup_str(word);
                // if word isn't already in wm, subscripting adds a new entry
                auto &lines = wm[word]; // lines is a shared_ptr
                if (!lines) // that pointer is null the first time we see word
                    lines.reset(new set); // allocate a new set
                lines->insert(n);      // insert this line number
    		}
    	}
    }
    
    // not covered in the book -- cleanup_str removes
    // punctuation and converts all text to lowercase so that
    // the queries operate in a case insensitive manner
    string TextQuery::cleanup_str(const string &word)
    {
        string ret;
        for (auto it = word.begin(); it != word.end(); ++it) {
            if (!ispunct(*it))
                ret += tolower(*it);
        }
        return ret;
    }
    
    QueryResult
    TextQuery::query(const string &sought) const
    {
    	// we'll return a pointer to this set if we don't find sought
    	static shared_ptr> nodata(new set);
    
        // use find and not a subscript to avoid adding words to wm!
        auto loc = wm.find(cleanup_str(sought));
    
    	if (loc == wm.end())
    		return QueryResult(sought, nodata, file);  // not found
    	else
    		return QueryResult(sought, loc->second, file);
    }
    
    ostream &print(ostream & os, const QueryResult &qr)
    {
        // if the word was found, print the count and all occurrences
        os << qr.sought << " occurs " << qr.lines->size() << " "
           << make_plural(qr.lines->size(), "time", "s") << endl;
    
        // print each line in which the word appeared
    	for (auto num : *qr.lines) // for every element in the set
    		// don't confound the user with text lines starting at 0
            os << "\t(line " << num + 1 << ") "
    		   << qr.file.begin().deref(num) << endl;
    
    	return os;
    }
    
    // debugging routine, not covered in the book
    void TextQuery::display_map()
    {
        auto iter = wm.cbegin(), iter_end = wm.cend();
    
        // for each word in the map
        for ( ; iter != iter_end; ++iter) {
            cout << "word: " << iter->first << " {";
    
            // fetch location vector as a const reference to avoid copying it
            auto text_locs = iter->second;
            auto loc_iter = text_locs->cbegin(),
                            loc_iter_end = text_locs->cend();
    
            // print all line numbers for this word
            while (loc_iter != loc_iter_end)
            {
                cout << *loc_iter;
    
                if (++loc_iter != loc_iter_end)
                     cout << ", ";
    
             }
    
             cout << "}\n";  // end list of output this word
        }
        cout << endl;  // finished printing entire map
    }
    • my_TextQuery.h

    #ifndef TEXTQUERY_H
    #define TEXTQUERY_H
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "my_QueryResult.h"
    
    /* this version of the query classes includes two
     * members not covered in the book:
     *   cleanup_str: which removes punctuation and
     *                converst all text to lowercase
     *   display_map: a debugging routine that will print the contents
     *                of the lookup mape
    */
    
    class QueryResult; // declaration needed for return type in the query function
    class TextQuery {
    public:
    	using line_no = std::vector::size_type;
    	TextQuery(std::ifstream&);
        QueryResult query(const std::string&) const;
        void display_map();        // debugging aid: print the map
    private:
        StrBlob file; // input file
        // maps each word to the set of the lines in which that word appears
        std::map>> wm;
    
    	// canonicalizes text: removes punctuation and makes everything lower case
        static std::string cleanup_str(const std::string&);
    };
    
    #endif
    • make_plural.h

    #include 
    using std::size_t;
    
    #include 
    using std::string;
    
    #include 
    using std::cout; using std::endl;
    
    #ifndef MAKE_PLURAL_H
    #define MAKE_PLURAL_H
    
    // return the plural version of word if ctr is greater than 1
    inline
    string make_plural(size_t ctr, const string &word, 
                                   const string &ending)
    {
    	return (ctr > 1) ? word + ending : word;
    }
    
    #endif
    • my_QueryResult.h

    #ifndef QUERYRESULT_H
    #define QUERYRESULT_H
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "my_StrBlob.h"
    
    class QueryResult {
    friend std::ostream& print(std::ostream&, const QueryResult&);
    public:
    	typedef std::vector::size_type line_no;
    	typedef std::set::const_iterator line_it;
    	QueryResult(std::string s,
    	            std::shared_ptr> p,
    	            StrBlob f):
    		sought(s), lines(p), file(f) { }
    	std::set::size_type size() const  { return lines->size(); }
    	line_it begin() const { return lines->cbegin(); }
    	line_it end() const   { return lines->cend(); }
    	StrBlob get_file() { return file; }
    private:
    	std::string sought;  // word this query represents
    	std::shared_ptr> lines; // lines it's on
    	StrBlob file;  //input file
    };
    
    std::ostream &print(std::ostream&, const QueryResult&);
    
    #endif
    • my_StrBlob.h

    #ifndef MY_STRBLOB_H
    #define MY_STRBLOB_H
    
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    //Declare in advance, which is required by friend declaration in strblob
    class StrBlobPtr;
    
    class StrBlob
    {
        friend class StrBlobPtr;
    public:
        typedef vector::size_type size_type;
        StrBlob();
        StrBlob(initializer_list il);
        StrBlob(vector *p);
        size_type size() const { return data->size(); }
        bool empty() const { return data->empty(); }
        //Add and remove elements
        void push_back(const string &t) {data->push_back(t);}
        void pop_back();
        //Element access
        string& front();
        const string& front() const;
        string& back();
        const string& back() const ;
    
        //Interface provided to strblobptr
        Strblobptr begin(); // these two functions can only be defined after defining strblobptr
        StrBlobPtr end();
        //Const version
        StrBlobPtr begin() const;
        StrBlobPtr end() const;
    private:
        shared_ptr> data;
        //If the data [i] is illegal, an exception is thrown
        void check(size_type i, const std::string &msg) const;
    };
    
    inline StrBlob::StrBlob(): data(make_shared>()) { }
    inline StrBlob::StrBlob(initializer_list il) : data(make_shared>(il)) { }
    inline StrBlob::StrBlob(vector *p): data(p) { }
    
    inline void StrBlob::check(size_type i, const string &msg) const
    {
        if (i >= data->size())
        {
            throw out_of_range(msg);
        }
    }
    
    inline string& StrBlob::front()
    {
        //If vector is empty, check throws an exception
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    //Const versionfront
    inline const string& StrBlob::front() const
    {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    inline string& StrBlob::back()
    {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    //Const versionback
    inline const string& StrBlob::back() const
    {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    inline void StrBlob::pop_back()
    {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }
    
    //Strblobptr throws an exception when trying to access a nonexistent element
    class StrBlobPtr
    {
        friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
    public:
        StrBlobPtr(): curr(0) { }
        StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }
        StrBlobPtr(const StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }
    
        string& deref() const;
        string& deref(int off) const;
        Strblobptr & incr(); // prefix increment
        Strblobptr & decr(); // prefix decrement
    private:
        //If the check is successful, check returns a shared to vector_ ptr
        shared_ptr> check(size_t, const string&) const;
    
        //Save a weak_ PTR, which means that the underlying vector may be destroyed
        weak_ptr> wptr;
        size_ T curr; // the current position in the array
    };
    
    inline shared_ptr> StrBlobPtr::check(size_t i, const string &msg) const
    {
        auto ret =  wptr.lock (); // does vector still exist?
        if (!ret)
        {
           throw runtime_error("unbound StrBlobPtr");
        }
        if (i >= ret->size())
        {
           throw out_of_range(msg);
        }
        Return; // otherwise, returns the shared to the vector_ ptr
    }
    
    inline string& StrBlobPtr::deref() const
    {
        auto p = check(curr, "dereference past end");
        Return (* P) [curr]; // (* P) is the vector that the object points to
    }
    
    inline string& StrBlobPtr::deref(int off) const
    {
        auto p = check(curr + off, "dereference past end");
        Return (* P) [curr + off]; // (* P) is the vector that the object points to
    }
    
    //Prefix increment: returns the reference of the incremented object
    inline StrBlobPtr& StrBlobPtr::incr()
    {
        //If curr is already pointing to the end of the container, it cannot be incremented
        check(curr, "increment past end of StrBlobPtr");
        ++Curr; // advance current position
        return *this;
    }
    
    //Prefix decrement: returns the reference of the decremented object
    inline StrBlobPtr& StrBlobPtr::decr()
    {
        //If curr is already 0, decreasing it will produce an illegal subscript
        --Curr; // decrement current position
        check(-1, "decrement past begin of StrBlobPtr");
        return *this;
    }
    
    //The definition of begin and end members of strblob
    inline StrBlobPtr StrBlob::begin()
    {
        return StrBlobPtr(*this);
    }
    
    inline StrBlobPtr StrBlob::end()
    {
      	auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
    
    //Const version
    inline StrBlobPtr StrBlob::begin() const
    {
    	  return StrBlobPtr(*this);
    }
    
    inline StrBlobPtr StrBlob::end() const
    {
        auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
    
    //Comparison operation of strblobptr
    inline bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
    {
        auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
        //If the underlying vectors are the same
        if (l == r)
        {
        	  //Then both pointers are empty, or they are equal when they point to the same element
        	  return (!r || lhs.curr == rhs.curr);
        }
        else
        {
        	  Return false; // if it points to different vectors, it cannot be equal
        }
    }
    
    inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
    {
    	  return !eq(lhs, rhs);
    }
    
    #endif

Recommended Today

Rust and python: why rust can replace Python

In this guide, we compare the rust and python programming languages. We will discuss the applicable use cases in each case, review the advantages and disadvantages of using rust and python, and explain why rust might replace python. I will introduce the following: What is rust? What is Python? When to use rust When to […]