For those of you familiar with the book Accelerated C++, I was writing a solution to problem 14-5 and came across some interesting behavior that I can't explain.
The problem involves using custom string and pointer/reference counter classes to implement a program that can concatenate vectors of strings and create pictures out of them.
Essentially, the part of the program in question is the following:
int main()
{
vec<str> test;
str s;
while(getline(std::cin,s))
{
test.push_back(str(s.begin(),s.end()));
//test.push_back(s); // This line doesn't work here - why?
// Using the above line results in every str in test being
// the empty string
}
// Use the vec<str> to make pictures
}
It seems as though my reference counter isn't working properly when I use the commented line: the result I get is as if every str
in test
were the empty string.
Here are my implementations of getline
and the relevant parts of the str
and ptr
classes:
str
class:
class str
{
friend std::istream& getline(std::istream &is, str &s);
public:
typedef char* iterator;
typedef const char* const_iterator;
typedef size_t size_type;
str() : data(new vec<char>) { }
str(size_type n, char c) : data(new vec<char>(n,c)) { }
str(const char *cp) : data(new vec<char>)
{
std::copy(cp,cp+std::strlen(cp),std::back_inserter(*data));
}
template <class InputIterator>
str(InputIterator b, InputIterator e) : data(new vec<char>)
{
std::copy(b,e,std::back_inserter(*data));
}
// Other str member functions and operators
private:
ptr< vec<char> > data;
};
ptr
class:
template <class T>
class ptr
{
public:
void make_unique()
{
if(*refptr != 1)
{
--*refptr;
refptr = new std::size_t(1);
p = p ? clone(p) : 0;
}
}
ptr() : p(0), refptr(new std::size_t(1)) { }
ptr(T* t) : p(t), refptr(new std::size_t(1)) { }
ptr(const ptr &h) : p(h.p), refptr(h.refptr) { ++*refptr; }
ptr& operator=(const ptr &);
~ptr();
T& operator*() const
{
if(p)
{
return *p;
}
throw std::runtime_error("unbound ptr");
}
T* operator->() const
{
if(p)
{
return p;
}
throw std::runtime_error("unbound ptr");
}
private:
T* p;
std::size_t* refptr;
};
template <class T>
ptr<T>& ptr<T>::operator=(const ptr &rhs)
{
++*rhs.refptr;
// free the left hand side, destroying pointers if appropriate
if(--*refptr == 0)
{
delete refptr;
delete p;
}
// copy in values from the right-hand side
refptr = rhs.refptr;
p = rhs.p;
return *this;
}
template <class T>
ptr<T>::~ptr()
{
if(--*refptr == 0)
{
delete refptr;
delete p;
}
}
The vec
class is essentially a subset of std::vector
. I can supply those details here too, if necessary.
And here is getline:
std::istream& getline(std::istream &is, str &s)
{
s.data->clear();
char c;
while(is.get(c))
{
if(c != '\n')
{
s.data->push_back(c);
}
else
{
break;
}
}
return is;
}
Even though you are counting references correctly, you are still sharing the same pointer between the instances. So
getline
is modifying the samestr
object. You need to implement Copy-on-write instr
.Here is what's wrong:
So, you should do:
instead of clearing the shared instance.