1

I'm currently reading c++ primer 5th edition and these is one of code examples that were in the book. I'm confused with this line of code auto ret = StrBlobPtr(*this, data->size()); if I understand correctly this line creates a temporary StrBlobPtr object and it calls this constructor StrBlobPtr(StrBlob& a, size_t sz = 0) : wptr(a.data), curr(sz) {} but I don't understand howauto ret=is getting a return value from the temporary object, so my questions are

  1. How is ret getting the object that was created by StrBlobPtr?
  2. How is end returning a StrBlobPtr that holds the last value in the std::shared_ptr<vector<string>> data; inside the StrBlob class.

The section of code I'm talking about is all the way at the bottom.

#pragma once

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

using std::vector;
using std::string;

class StrBlobPtr;

class StrBlob {
public:
    using size_type = vector<string>::size_type;
    friend class StrBlobPtr;

    StrBlobPtr begin();
    StrBlobPtr end();

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

    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }

    void push_back(const string& t) { data->push_back(t); }
    void pop_back()
    {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }

    std::string& front()
    {
        check(0, "front on empty StrBlob");
        return data->front();
    }

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

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

private:
    void check(size_type i, const string& msg) const
    {
        if (i >= data->size()) throw std::out_of_range(msg);
    }
private:
    std::shared_ptr<vector<string>> data;
};

class StrBlobPtr {
public:
    StrBlobPtr() : curr(0) {}
    StrBlobPtr(StrBlob& a, size_t sz = 0) : wptr(a.data), curr(sz) {}
    bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }
    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;
    }

private:
    std::shared_ptr<vector<string>> check(size_t i, const 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;
    }
    std::weak_ptr<vector<string>> wptr;
    size_t curr;
};
StrBlobPtr StrBlob::begin() { return StrBlobPtr(*this); }
StrBlobPtr StrBlob::end()
{
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}
user4581301
  • 29,019
  • 5
  • 26
  • 45
Nice
  • 91
  • 1
  • 8
  • Initialization in C++ is... [complicated](https://en.cppreference.com/w/cpp/language/initialization). In this case you get [Copy Initialization](https://en.cppreference.com/w/cpp/language/copy_initialization). – user4581301 Jun 17 '20 at 19:27
  • Thank you for your answer. – Nice Jun 17 '20 at 20:45
  • `auto x = T();` is the same as `T x();` (except not interpreted as a function declaration) – M.M Jun 17 '20 at 21:21

1 Answers1

2

Since c++11 you can use keyword auto if you don't want to specify type of variable explicitly, but don't confuse it with automatic variables, which means that they are deleted at the end of the block (you can read this: cppreference).

So at first line of function StrBlobPtr::end() constructor is called, object of type StrBlobPtr is created, after that copy constructor is called, another object is created but this object will exist until the end of block is reached (in this case end of function). First object is deleted after that, so you have only object created by the copy constructor. Please check this post:link.

Your second question is about returning object, if I am right. You must be careful when you do this in c++. Suppose you called StrBlobPtr::end() somewhere in your code:

StrBlobPtr obj; 
obj = StrBlobPtr::end();

So you created object in first line, after that you call function, object of the same type is returned but this object exists only in this line, after StrBlobPtr::operator=(StrBlobPtr x) is called object returned from function is deleted and it doesn't exists in memory, so you can't do:

StrBlobPtr* obj; 
obj = StrBlobPtr::end();

because after second line pointer obj have address of deleted object, and if you try to use it, you will get exception. So you must be careful if you want to return whole object from function, sometimes it is better to create object in dynamic memory with operator new and to return reference or pointer from function. It is important if you call this function often, every time you need to create object and to call copy constructor to return this object.

  • 1
    Thank you for your answer I was thinking about it the wrong way, I thought the temporary object created by **StrBlobPtr** was delete immediately after it was called and it was never copied into **ret**. – Nice Jun 17 '20 at 20:43
  • 1
    @verynice note that the compiler is smart and will likely eliminate all of the copying implied in the answer to 1. `ret` will be constructed directly instead of copying the temporary and the temporary object will not exist at all. This behaviour is required by C++17 and more recent, but often observed by compilers under older Standards – user4581301 Jun 17 '20 at 21:02