C++ Primer, Chapter 12, section 12.1, exercise answer 2

Chapter 12 dynamic memory of C++ Primer

Section 12.1 answers to dynamic memory and smart pointer exercises

Exercise 12.11: what happens if we call process like this?

process(shared_ptr<int>(p.get()));

[Topic setting ideas]

Understand that smart pointers and ordinary pointers cannot be mixed.

[answer]

This call is wrong. p.get() gets a normal pointer to the int object shared by P. Use this pointer to create a shared_ptr instead of using P to create a shared_ptr, the correct dynamic object sharing will not be formed. The compiler will think that P and PTR are two unrelated shared created with two addresses (although they are equal)_ PTR instead of sharing the same dynamic object. Thus, the reference count for both is 1. After the process is executed, the reference count of PTR is reduced to 0, and the managed memory address is released, which is managed by P. P becomes a shared that manages dangling pointers_ ptr.

Exercise 12.12: the definitions of p and sp are as follows. For each subsequent call to process, if it is legal, explain what it does. If it is not legal, explain the cause of the error:

auto p = new int();
auto sp = make_shared<int>();
(a) process(sp);
(b) process(new int());
(c) process(p);
(d) process(shared_ptr<int>(p));

[answer]

(a) Legal. sp is a shared pointer to an int object. The call to process copies sp and passes the parameter ptr to process. Both point to the same int object, and the reference count becomes 2. When the process is completed, the ptr is destroyed and the reference count changes back to 1.

(b) Legal. new creates an int object, and the pointer to it is used to create a shared object_ ptr, the parameter ptr passed to process, and the reference count is 1. When the process is completed, the ptr is destroyed, the reference count becomes 0, and the temporary int object is destroyed. There are no memory leaks and dangling pointers.

(c) Illegal. Cannot convert int * to shared_ ptr<int>.

(d) Legal, but wrong program. p is a normal pointer to an int object and is used to create a temporary shared object_ ptr, the parameter ptr passed to process, and the reference count is 1. When the process is completed, the ptr is destroyed, the reference count becomes 0, and the int object is destroyed. p becomes a dangling pointer.

Exercise 12.13: what happens if you execute the following code?

auto sp = make_shared<int>();
auto p = sp.get();
delete p;

[Topic setting ideas]

Continue to understand the problems in the use of smart pointers and ordinary pointers.

[answer]

The second line uses get to get the address of the int object pointed to by sp, and the third line uses delete to release the address. This means that the reference count of sp is still 1, but the int object it points to has been released. sp becomes a shared like a null pointer_ ptr.

Exercise 12.14: write your own version of shared_ptr manages the function of connection.

[Topic setting ideas]

This exercise uses smart pointers to manage classes that use resources to avoid memory leaks.

[answer]

Refer to this section to design functions. The main function calls unused and used shared respectively_ PTR version. According to the output, the former does not call disconnect, while the latter calls. Note the newline in the output of f1. Obviously, disconnect is called when p is destroyed after f1 is completed (the last statement that outputs newline has been executed).

#include <iostream>
#include <memory>

using namespace std;

struct destination {};
struct connection {};

connection connect(destination *pd)
{
    cout << "open a connection" << endl;
    return connection();
}

void disconnect(connection c)
{
    cout << "Close connection" << endl;
}

//Shared not used_ PTR version
void f(destination &d)
{
    cout << "Direct management connect" << endl;
    connection c = connect(&d);
    //Forget to call disconnect to close the connection
    cout << endl;
}

void end_connection(connection *p)
{
    disconnect(*p);
}

//Using shared_ptr version
void f1(destination &d)
{
    cout << "use shared_ptr Administration connect" << endl;
    connection c = connect(&d);

    shared_ptr<connection> p(&c, end_connection);
    //Forget to call disconnect to close the connection
    cout << endl;
}

int main(int argc, const char * argv[])
{
    destination d;
    f(d);
    f1(d);
    return 0;
}

Operation results:

Exercise 12.15: rewrite the procedure of the previous question and replace end with lambda (see section 10.3.2, page 346)_ Connection function.

[Topic setting ideas]

Review lambda.

[answer]

According to end_ For the definition of connection, lambda does not capture local variables. The parameter is the connection pointer. Call disconnect with the object pointed to by the pointer:

#include <iostream>
#include <memory>


using namespace std;

struct destination {};
struct connection {};

connection connect(destination *pd)
{
    cout << "open a connection" << endl;
    return connection();
}


void disconnect(connection c)
{
    cout << "Close connection" << endl;
}

//Shared not used_ PTR version
void f(destination &d)
{
    cout << "Direct management connect" << endl;
    connection c = connect(&d);
    //Forget to call disconnect to close the connection
    cout << endl;
}

void end_connection(connection *p)
{
    disconnect(*p);
}

//Using shared_ptr version
void f1(destination &d)
{
    cout << "use shared_ptr Administration connect" << endl;
    connection c = connect(&d);
    shared_ptr<connection> p(&c, [](connection *p) {disconnect(*p); });
    //Forget to call disconnect to close the connection
    cout << endl;
}

int main(int argc, const char * argv[])
{
    destination d;
    f(d);
    f1(d);
    return 0;
}

Operation results:

  Exercise 12.16: if you try to copy or assign unique_ptr, the compiler does not always give understandable error messages. Write a program that contains this error and observe how the compiler diagnoses this error.

[Topic setting ideas]

Deep understanding of unique_ptr cannot be copied or assigned.

[answer]

#include <iostream>
#include <memory>
#include <string>

using namespace std;

int main()
{
    std::unique_ptr<string> str1(new string("23"));
    std::unique_ptr<string> str2;
    str2 = str1;

    return 0;
}

This error message appears in the VS compiler

  That is, the program calls the deleted function. The reason is that the standard library prohibits unique_ Copy and assign PTR, and declare its copy constructor and assignment function as delete:

unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;

Exercise 12.17: the following unique_ In the PTR statement, which are legal and which may lead to subsequent program errors? Explain where each wrong problem is.

int ix = 1024, *pi = &ix, *pi2 = new int(2048);
typedef unique_ptr<int> IntP;
(a) IntP p0(ix);                                (b) IntP p1(pi);
(c) IntP p2(pi2);                               (d) IntP p3(&ix);
(e) IntP p4(new int(2048));                     (f) IntP p5(p2.get());

[Topic setting ideas]

Continue to familiarize yourself with unique_ Problems needing attention in the use of PTR.

[answer]

(a) Illegal. unique_ptr needs to be initialized with a pointer and cannot convert int to pointer.

(b) Legal. IntP can be initialized with an int *, but this program is logically wrong. It initializes p1 with the address of an ordinary int variable. When p1 is destroyed, it will release this memory, and its behavior is undefined.

(c) Legal. It is correct to initialize IntP with a pointer to a dynamically allocated object. (d) Legal. However, there are the same problems as (b).

(d) Legal. However, there are the same problems as (b).

(e) Legal. Similar to (c).

(f) Legal. However, p5 is initialized with the address of the object managed by p2, resulting in two unique_ptr points to the same memory address. When one of them is unique_ When PTR is destroyed (or reset is called to release the object), the memory is released and another unique_ptr becomes a null pointer.

Exercise 12.18: shared_ Why does PTR have no release members?

[Topic setting ideas]

Understanding unique_ptr and shared_ The difference of PTR.

[answer]

unique_ptr "monopolizes" the ownership of objects and cannot be copied or assigned. The release operation is used to transfer ownership of an object to another unique object_ PTR. And multiple shared_ptr can "share" ownership of objects. When sharing is needed, you can simply copy and assign values. Therefore, an operation such as release is not required to transfer ownership.

Exercise 12.19: define your own version of StrBlobPtr, update the StrBlob class, and add the appropriate friend declaration and begin and end members.

[Topic setting ideas]

Familiar with weak_ Use of PTR.

[answer]

Refer to this section to implement the required procedures. In order to traverse the elements in the vector of StrBlob, eq and neq functions are also defined to compare whether two strblobptrs point to the same position of the same vector.

#ifndef SYSTRBLOB_19_H
#define SYSTRBLOB_19_H

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>

using namespace std;

//Declaration in advance, required for friend class declaration in StrBlob
class StrBlobPtr;

class StrBlob
{
    friend class StrBlobPtr;

public:
    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> i1);
    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 StrBlobPtr is defined
    StrBlobPtr end();

private:
    shared_ptr<std::vector<std::string>> 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<vector<string>>()) { }

StrBlob::StrBlob(initializer_list<string> i1): data(make_shared<vector<string>>(i1)) { }

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 the vector is empty, check throws an exception
    check(0, "front on empty StrBlob");
    return data->front();
}

//const version front
inline const string& StrBlob::front() const
{
    check(0, "front on empty StrBlob");
    return data->back();
}

inline string& StrBlob::back()
{
    check(0, "back on emyty StrBlob");
    return data->back();
}

//const version back
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 non-existent 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) { }

    string& deref() const;
    StrBlobPtr& incr(); //Prefix increment
    StrBlobPtr& decr(); //Prefix decrement

private:
    //If the check is successful, check returns a shared to the vector_ ptr
    shared_ptr<vector<string>> check(size_t, const string&) const;

    //Save a break_ PTR, which means that the underlying vector may be destroyed
    weak_ptr<vector<string>> wptr;
    size_t curr;//Current position in the array
};

inline shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
    auto ret = wptr.lock();//Does the vector still exist?
    if(!ret)
        throw runtime_error("unbound StrBlobPtr");
    if(i >= ret->size())
        throw out_of_range(msg);
    return ret;//Otherwise, it 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 to which the object points
}

//Prefix increment: returns the reference of the incremented object
inline StrBlobPtr& StrBlobPtr::incr()
{
    //If curr already points 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, decrementing it will produce an illegal subscript
    --curr;//Decrement current position
    check(-1, "decrement past begin of StrBlobPtr");
    return *this;
}

//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;
}

//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 vector is the same
    if(l == r)
        //Both pointers are null, or when they point to the same element, they are equal
        return (!r || lhs.curr == rhs.curr);
    else
        return false;//If you point to different vector s, they cannot be equal
}

inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
    return !eq(lhs, rhs);
}

#endif // SYSTRBLOB_19_H
#include <iostream>
#include "SYStrBlob_19.h"

int main(int argc, const char * argv[])
{
    StrBlob b1;
    {
        StrBlob b2 = {"a", "an", "the"};
        b1 = b2;
        b2.push_back("about");
        cout << b2.size() << endl;
    }
    cout << b1.size() << endl;
    cout << b1.front() << " " << b1.back() << endl;

    const StrBlob b3 = b1;
    cout << b3.front() << " " << b3.back() << endl;

    for(auto it = b1.begin(); neq(it, b1.end()); it.incr())
        cout << it.deref() <<endl;

    return 0;
}

Operation results:

  Exercise 12.20: write a program, read in an input file line by line, store the content in a StrBlob, and print out each element in the StrBlob with a StrBlobPtr.

[Topic setting ideas]

This exercise uses StrBlob and StrBlobPtr.

[answer]

Read the input file line by line with getline, store it in StrBlob, traverse from start to end of StrBlob with StrBlobPtr, and print each string one by one.

#include <iostream>
#include <fstream>
#include "SYStrBlob_19.h"

using namespace std;


int main(int argc, const char * argv[]) {

    ifstream in(argv[1]);
    cout << "argv[1]=============" << argv[1] << endl;
    if(!in)
    {
        cout << "Unable to open input file" << endl;
        return -1;
    }

    StrBlob b;
    string s;
    while(getline(in, s))
    {
        b.push_back(s);
    }

    for(auto it = b.begin(); neq(it, b.end()); it.incr())
    {
        cout << it.deref() << endl;
    }

    return 0;
}

data12_ The contents of 20.txt file are as follows:

Alice Emma has long flowing red hair.
Her Daddy says when the wind blows
through her hair, it looks almost alive,
like a fiery bird in flight.
A beautiful fiery bird, he tells her,
magical but untamed.
"Daddy, shush, there is no such thing,"
she tells him, at the same time wanting
him to tell her more.
Shyly, she asks, "I mean, Daddy, is there?"

Set command line parameters:

  Operation results:

  Exercise 12.21: you can also write deref members of StrBlobPtr as follows:

std::string& deref() const
{ return (*check(curr, "dereference past end"))[curr]; }

Which version do you think is better? Why?

[Topic setting ideas]

Think about the advantages and disadvantages of different ways to check legitimacy.

[answer]

The way in the book is better. The validity check is separated from the element acquisition and return statements, and the code is clearer and easier to read. When the second statement is executed, it is ensured that p is the existing vector and curr is the legal position, so the element can be safely obtained and returned. This clear structure is also more conducive to modifying different processing logic. The version in this question combines legitimacy check and element acquisition and return in one sentence, which is not easy to read and modify.

Exercise 12.22: how do you think you should modify StrBlobPtr to use const StrBlob? Define a class named ConstStrBlobPtr to point to const StrBlob.

[Topic setting ideas]

This exercise designs const version.

[answer]

First, define a constructor for StrBlobPtr that can accept const strblob & parameters:

[illustration]

Secondly, define begin and end that can operate const objects for StrBlob. Statement:

StrBlobPtr(const StrBlob &a, size_t sz=0): wptr(a.data), curr(sz) {}

Secondly, define begin and end that can operate const objects for StrBlob.

Statement:

StrBlobPtr begin() const;
StrBlobPtr end() const;

definition:

inline StrBlobPtr StrBlob::begin() const
{
	return StrBlobPtr(*this);
}

inline StrBlobPtr StrBlob::end() const
{
	auto ret = StrBlobPtr(*this, data->size());
	return ret;
}

The complete implementation is as follows:

#ifndef SYSTRBLOB_22_H
#define SYSTRBLOB_22_H

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>

using namespace std;

//Declaration in advance, required for friend class declaration in StrBlob
class StrBlobPtr;

class StrBlob
{
    friend class StrBlobPtr;

public:
    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> i1);
    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() const; //These two functions can only be defined after StrBlobPtr is defined
    StrBlobPtr end() const;

private:
    shared_ptr<std::vector<std::string>> 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<vector<string>>()) { }

StrBlob::StrBlob(initializer_list<string> i1): data(make_shared<vector<string>>(i1)) { }

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 the vector is empty, check throws an exception
    check(0, "front on empty StrBlob");
    return data->front();
}

//const version front
inline const string& StrBlob::front() const
{
    check(0, "front on empty StrBlob");
    return data->back();
}

inline string& StrBlob::back()
{
    check(0, "back on emyty StrBlob");
    return data->back();
}

//const version back
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 non-existent element
class StrBlobPtr
{
    friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
public:
    StrBlobPtr(): curr(0) { }
                //Add here
    StrBlobPtr(const StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }

    string& deref() const;
    StrBlobPtr& incr(); //Prefix increment
    StrBlobPtr& decr(); //Prefix decrement

private:
    //If the check is successful, check returns a shared to the vector_ ptr
    shared_ptr<vector<string>> check(size_t, const string&) const;

    //Save a break_ PTR, which means that the underlying vector may be destroyed
    weak_ptr<vector<string>> wptr;
    size_t curr;//Current position in the array
};

inline shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
    auto ret = wptr.lock();//Does the vector still exist?
    if(!ret)
        throw runtime_error("unbound StrBlobPtr");
    if(i >= ret->size())
        throw out_of_range(msg);
    return ret;//Otherwise, it 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 to which the object points
}

//Prefix increment: returns the reference of the incremented object
inline StrBlobPtr& StrBlobPtr::incr()
{
    //If curr already points 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, decrementing it will produce an illegal subscript
    --curr;//Decrement current position
    check(-1, "decrement past begin of StrBlobPtr");
    return *this;
}

//Definition of begin and end members of StrBlob
inline StrBlobPtr StrBlob::begin() const//Add here
{
    return StrBlobPtr(*this);
}

inline StrBlobPtr StrBlob::end() const//Add here
{
    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 vector is the same
    if(l == r)
        //Both pointers are null, or when they point to the same element, they are equal
        return (!r || lhs.curr == rhs.curr);
    else
        return false;//If you point to different vector s, they cannot be equal
}

inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
    return !eq(lhs, rhs);
}


#endif // SYSTRBLOB_22_H
#include <iostream>
#include <fstream>
#include "SYStrBlob_22.h"

using namespace std;

int main(int argc, const char * argv[])
{
    const StrBlob b = {"Hello", "World", "!"};

    for(auto it = b.begin(); neq(it, b.end()); it.incr())
    {
        cout << it.deref() << endl;
    }

    return 0;
}

Operation results:

Posted by nOw on Sat, 09 Oct 2021 18:36:26 -0700