2

Hi i am reading C++ primer 5th addition and have some doubts in the section of weak_ptr. It is written that

By using a weak_ptr, we don’t affect the lifetime of the vector to which a given StrBlob points. However, we can prevent the user from attempting to access a vector that no longer exists.

Then they have given the following code as an example:

#include<iostream>
#include<string>
#include<vector>
#include<memory>
#include<initializer_list>
using namespace std;
class StrBlobPtr;

class StrBlob {
    friend class StrBlobPtr;

public:
    typedef std::vector<std::string>::size_type size_type;
    StrBlob():data(std::make_shared<std::vector<std::string>>()){

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

    }
    size_type size() const {
        return data->size();
    }
    bool empty() const {
        return data->empty();
    }
    void push_back(const std::string &t){
        data->push_back(t);
    }
    std::string& front(){
        check(0,"front on empty StrBlob");
        return data->front();
    }
    std::string& front() const{
        check(0,"front on const empty StrBlob");
        return data->front();
    }
    std::string& back(){
        check(0,"back on empty StrBlob");
        return data->back();
    }
    std::string& back() const {
        check(0,"back on const empty StrBlob");
        return data->back();
    }
    void pop_back(){
        check(0,"pop_back on empty StrBlob");
        data->pop_back();
    }
private:
    std::shared_ptr<std::vector<std::string>> data;
    void check(size_type i, const std::string &msg) const{
        if(i >= data->size()){
            throw out_of_range(msg);
        }
    }
    StrBlobPtr begin();
    StrBlobPtr end();
};


class StrBlobPtr {
    public:
        typedef std::vector<std::string>::size_type size_type;
        StrBlobPtr():curr(0){

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

        }
        std::string& deref() const {
            auto p = check(curr, "dereference past end");
            return (*p)[curr];
        }
        StrBlobPtr& incr(){
            check(curr, "increment past end of StrBlobPtr");
            ++curr;
            return *this;
        }
        std::shared_ptr<std::vector<std::string>> check(std::size_t i, const std::string &msg) const{
            auto ret = wptr.lock();
            if(!ret){
                throw std::runtime_error("unbound StrBlobPtr");
            }
            if(i>= ret->size()){
                throw std::out_of_range(msg);
            }
            return ret;
        }
    private:
        std::weak_ptr<std::vector<std::string>> wptr;
        size_type curr;
};

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

int main(){
    

    return 0;
}

My questions are as follows:

  1. How can we prevent the user from attempting to access a vector that no longer exists? I can't come up with a use case,how can we use the above quoted statement in this example?
  2. How does this example shows/verifies that we can prevent the user from attempting to access a vector that no longer exists? *If this example does not shows what they have written then why is this example there in the book?*Note that i have written if.
Jason
  • 36,170
  • 5
  • 26
  • 60
  • Have you looked up what happens when you do `auto ret = wptr.lock()`? – NathanOliver Mar 08 '21 at 13:14
  • Yes i know what happens when we write `auto ret = wptr.lock()` and i understand what is happening in this statement. Here we are checking if there are any shared_ptr pointing to the same memory to which wptr points and depending upon the result we get a nullptr or a share_ptr. – Jason Mar 08 '21 at 13:18
  • @JasonLiam followed by the line where `ret` is tested to see if the shared_ptr has already released the object `if(!ret){` Note that the control block persists until all shared and weak pointers have been destroyed. – Richard Critten Mar 08 '21 at 13:28
  • 1
    Well the `main()` function is empty. So the program does exactly nothing. In other words yes, it's a very bad example. Maybe [this](https://stackoverflow.com/questions/12030650/when-is-stdweak-ptr-useful) will help? – rustyx Mar 08 '21 at 13:28
  • @rustyx yeah i read that question also which you suggested, before posting this question. I understand why we use weak_ptr, just wanted to know if they(primer) should have given something in the main() loop also so that we can see the pointers in action. Thanks – Jason Mar 08 '21 at 13:31
  • 1
    I guess it's a question of how you define *access to the vector*. In a multithreaded environment that `deref` function certainly won't stop the vector from going away before the string is manipulated via that potentially dangling reference. – StoryTeller - Unslander Monica Mar 08 '21 at 13:32
  • "By using a weak_ptr, we don’t affect the lifetime of the vector to which a given StrBlob points. " I think, this sentence has to be read with special care, I think it's not precise enough. At latest in a multithreaded environment, this is often not true as soon as weak_ptr.lock() is involved. – Secundi Mar 08 '21 at 13:33

1 Answers1

1

1. How can we prevent the user from attempting to access a vector that no longer exists?

We can prevent it by exchanging a weak_ptr for a shared_ptr. weak_ptr::lock() does that. It atomically checks if the pointed-to object still exists and increments the corresponding shared_ptr ref count, thus "blocking" any possible deletion from that point on.

So after this line:

        auto ret = wptr.lock();

ret will be a shared_ptr that either owns the object or doesn't, and that fact will not change for as long as ret exists.

Then with a simple test you can safely check if there is an object or not:

        if(!ret){
            /* no object anymore */
        }

At the end the function does return ret;, which returns a copy of it, thus still preventing an object from being deleted (ref count is again incremented and then decremented). So as long as you own an instance of shared_ptr, you can rest assured the object will continue to exist.

However, here we have a problem:

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

This returns a reference to std::string inside a vector which, after p goes out of scope is held only by weak_ptr, i.e. a potentially dangling reference (which is no different from a dangling pointer).

2. How does this example shows/verifies that we can prevent the user from attempting to access a vector that no longer exists?

Apparently it doesn't. Just ignore it.

rustyx
  • 80,671
  • 25
  • 200
  • 267