Thread: Class Definitions have Interwoven Dependency

  1. #1
    C++ Enthusiast M.Richard Tober's Avatar
    Join Date
    May 2011
    Location
    Georgia
    Posts
    56

    Question Class Definitions have Interwoven Dependency

    Hello, I have an exercise from my text that defines a StrBlob class, then a StrBlobPtr class to hold weak pointers to the StrBlobs. This is from C++ Primer (5th Edition) and coincidentally, the entire chapter is available on-line at here.

    My problem is that the begin and end functions of StrBlob can't be defined until the entire StrBlobPtr class is defined. Forward declarations don't cut it, since begin and end need more than pointers.

    The solution (if you also look at the errata for the book) seems to be to define StrBlob, leave begin and end undefined, then full define StrBlobPtr, and following that, finally define StrBlob::begin() and StrBlob::end().

    Anyhow, the above works, as I show in the included code below - but it seems like a hack and messy. What would be the proper way to do this? My text may be obfuscating the issue in the pursuit of pedagogy.

    Additionally, how would one separate StrBlob and StrBlobPtr into there own headers? I'd think it impossible, since the StrBlob would have to nestle an "#include "StrBlobPtr.hpp" in the center of it's own definition...?

    Code:
    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    #include <stdexcept>
    
    class StrBlobPtr; // Forward declaration
    
    class StrBlob {
      public:
        friend class StrBlobPtr;    
        typedef std::vector<std::string>::size_type size_type;
        
        StrBlob();
        explicit StrBlob(std::initializer_list<std::string> il);
        // StrBlob(std::initializer_list<std::string> il);
        
        size_type size() const { return data->size(); }
        bool empty() const { return data->empty(); }
        
        // add and remove elements
        void push_back(const std::string& s) { data->push_back(s); }
        void pop_back();
        
        // element access
        std::string& front();
        std::string& back();
        std::string& front() const;
        std::string& back() const;
        StrBlobPtr begin();
        StrBlobPtr end();
    
      private:
        std::shared_ptr<std::vector<std::string>> data;
        // throw -msg- if data[i] isn't valid
        void check(size_type i, const std::string& msg) const;
    };
    
    StrBlob::StrBlob()
        : data(std::make_shared<std::vector<std::string>>()) {}
    
    StrBlob::StrBlob(std::initializer_list<std::string> il)
        : data(std::make_shared<std::vector<std::string>>(il)) {}
    
    void StrBlob::check(size_type i, const std::string& msg) const {
        if (i >= data->size())
            throw std::out_of_range(msg);
    }
    
    std::string& StrBlob::front() {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    std::string& StrBlob::back() {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    std::string& StrBlob::front() const {
        check(0, "front on empty StrBlob");
        return data->front();
    }
    
    std::string& StrBlob::back() const {
        check(0, "back on empty StrBlob");
        return data->back();
    }
    
    void StrBlob::pop_back() {
        check(0, "pop_back on empty StrBlob");
        data->pop_back();
    }
    
    // StrBlobPtr throw an exception on attempts to access a nonexistent element
    class StrBlobPtr {
        public:
            StrBlobPtr() : curr(0) {}
            StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
            std::string& deref() const;
            StrBlobPtr& incr();    // prefix version
        private:
            // check returns a shared_ptr to the vector if the check succeeds
            std::shared_ptr<std::vector<std::string>>
                check(std::size_t, const std::string&) const;
            // store a weak_ptr, which means the underlying vector may be destroyed
            std::weak_ptr<std::vector<std::string>> wptr;
            std::size_t curr;    // current position within the array
    };
    
    std::string& StrBlobPtr::deref() const {
        auto p = check(curr, "dereference past end of StrBlobPtr");
        return (*p) [curr];        // (*p) is the vector to which this object points
    }
    
    StrBlobPtr& StrBlobPtr::incr() {
        // if curr already points to the end of the container, we can't increment it
        check(curr, "increment past end of StrBlobPtr");
        ++curr;                    // advance the current state
        return *this;
    }
    
    std::shared_ptr<std::vector<std::string>> StrBlobPtr::
    check(std::size_t i, const std::string& msg) const {
        auto ret = wptr.lock();        // is the vector still around?
        if(!ret) {                     // if not, throw
            throw std::runtime_error("Unbound StrBlobPtr");
        }
        if (i >= ret->size()) {        // if it is, is i in range?
            throw std::out_of_range(msg); // if not throw
        }
        return ret;                    // return shared_ptr to the vector
    }
    
    StrBlobPtr StrBlob::begin() {
        return StrBlobPtr(*this);
    }
    
    StrBlobPtr StrBlob::end() {
        auto ret = StrBlobPtr(*this, data->size());
        return ret;
    }
    
    int main() {
        return 0;
    }
    As I said, the above works (compiles, haven't -tested- it extensively) but it seems messy. Any comments, suggestions or concerns are welcome.
    Eventually, I decided that thinking was not getting me very far and it was time to try building.
    — Rob Pike, "The Text Editor sam"

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by M.Richard Tober
    The solution (if you also look at the errata for the book) seems to be to define StrBlob, leave begin and end undefined, then full define StrBlobPtr, and following that, finally define StrBlob::begin() and StrBlob::end().

    Anyhow, the above works, as I show in the included code below - but it seems like a hack and messy. What would be the proper way to do this?
    That is a proper way of doing what you want to do. Note that the member function definitions outside the class definition don't have to be in the order that you saw: you could, for example, define both classes first, then have all the member function definitions follow.

    Quote Originally Posted by M.Richard Tober
    Additionally, how would one separate StrBlob and StrBlobPtr into there own headers? I'd think it impossible, since the StrBlob would have to nestle an "#include "StrBlobPtr.hpp" in the center of it's own definition...?
    You are mistaken: the class definition is separate from the member function definitions. You can write them inline in the class definition, but if you don't, they can be anywhere after the class definition, or even be in a different translation unit, as would happen when you move the class definition to a header file and include the header file in multiple source files.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    C++ Enthusiast M.Richard Tober's Avatar
    Join Date
    May 2011
    Location
    Georgia
    Posts
    56
    Thank you so much laserlight! I split them into proper header/source file pairs, and everything compiled without a peep. (Well, -Weffc++ made a peep about wptr not being initialized in the initialization list of the default constructor of StrBlobPtr, lol) Excellent.

    I had gotten in the habit of writing the class declarations and member function definitions directly in my program's main source file to save time and effort. In other words, Geany's Project Management is eluding me. I'll redouble my efforts. I recall you mentioned you worked on Geany last year, so I looked into it. Been part of my code writing toolbox since.

    Edit: Geany uses my makefiles, and now I know! Super. =)
    Last edited by M.Richard Tober; 03-30-2013 at 01:00 AM. Reason: Geany Project Success
    Eventually, I decided that thinking was not getting me very far and it was time to try building.
    — Rob Pike, "The Text Editor sam"

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Class Methods Multipile Definitions
    By codebreather in forum C++ Programming
    Replies: 15
    Last Post: 08-22-2011, 09:53 AM
  2. Replies: 23
    Last Post: 03-07-2011, 05:28 PM
  3. VS9 Breakpoint a dependency?
    By Glorfindel in forum C++ Programming
    Replies: 2
    Last Post: 04-07-2009, 10:47 PM
  4. Replies: 7
    Last Post: 11-17-2008, 01:00 PM
  5. Splitting up class definitions
    By Dunners in forum C++ Programming
    Replies: 4
    Last Post: 02-21-2005, 12:13 PM

Tags for this Thread