F
Frederick Gotham
(This sounds like a tutorial at the start, but it's actually a question
when you get down into it...)
When writing slightly more elaborate classes, there are Things To Be Aware
Of.
A good example is "The Rule of Three". Basically, if you acquire a resource
in the constructor, then you're going to need to write a special copy-
constructor and assignment operator, e.g.:
(Before I begin, I'm going to write a small template in order to help me
default-initialise a dynamically allocated array -- functionality which is
lacking is C++.)
template<class T, unsigned long len>
struct DefInitArray {
T array[len];
DefInitArray() : array() {}
};
Now here's my code:
#include <cstring>
class ArbitraryClass {
protected:
static unsigned long const buflen = 1024;
char * const p_buf;
public:
ArbitraryClass() : p_buf( (new DefInitArray<char,buflen>[1])->array )
{}
ArbitraryClass(const ArbitraryClass &original) : p_buf(new char
[buflen])
{
std::memcpy(p_buf, original.p_buf, sizeof *p_buf * buflen);
}
ArbitraryClass &operator=(const ArbitraryClass &rhs)
{
std::memcpy(p_buf, rhs.p_buf, sizeof *p_buf * buflen);
}
~ArbitraryClass()
{
delete [] p_buf;
}
};
int main()
{
ArbitraryClass a;
ArbitraryClass b( a );
a = b;
}
Expanding on this, I'm going to rename the class to "StringStream", and
then I'm going to add five things to it:
(1) A pointer to the current position in the buffer called "pos".
(2) Code in the constructor, copy-constructor and assignment operator
which correctly sets the value of "pos".
(3) An operator<< in order to write to the stream.
(4) An IsFull() member function to test whether the buffer is full.
(5) A "Print" member function to print the entire buffer contents.
Here's the updated code:
template<class T, unsigned long len>
struct DefInitArray {
T array[len];
DefInitArray() : array() {}
};
#include <cstring>
#include <ostream>
#include <cstdlib>
#include <iostream>
class StringStream {
protected:
static unsigned long const buflen = 1024;
char * const p_buf;
char *pos;
public:
StringStream() :
p_buf( (new DefInitArray<char,buflen>[1])->array ),
pos( p_buf )
{}
StringStream(const StringStream &original) :
p_buf(new char[buflen]),
pos( p_buf + (original.pos - original.p_buf) )
{
std::memcpy(p_buf, original.p_buf, sizeof *p_buf * buflen);
}
StringStream &operator=(const StringStream &rhs)
{
std::memcpy(p_buf, rhs.p_buf, sizeof *p_buf * buflen);
pos = p_buf + (rhs.pos - rhs.p_buf);
}
bool IsFull() const
{
return pos == p_buf + (buflen - 1);
}
StringStream &operator<<( char const c )
{
if( !IsFull() ) *pos++ = c;
return *this;
}
StringStream &operator<<( const char *p )
{
for( ; *p; ++p) *this << *p;
}
void Print( std:
stream &out ) const
{
out << p_buf << '\n';
}
~StringStream()
{
delete [] p_buf;
}
};
int main()
{
StringStream a;
a << "Hello, my name's Fred";
a.Print( std::cout );
StringStream b( a );
b << ", and I'm a clone!";
b.Print( std::cout );
a = b;
a.Print( std::cout );
std::system("PAUSE");
}
Okay so my code works fine, so what's missing?
What happens if a StringStream object gets re-located in memory (perhaps by
a swap)? If it were to be re-located, its "pos" member would effectively
become corrupt.
So my question is:
What are the Thing To Be Aware Of when storing an object's address
within itself (or the address of a member object, or base class, etc.)?
Secondly:
What exactly do I have to do to my StringStream class in order for it
to survive a relocation? Would I be right in thinking I've to do something
like:
namespace std {
void swap( StringStream&, StringStream& );
}
when you get down into it...)
When writing slightly more elaborate classes, there are Things To Be Aware
Of.
A good example is "The Rule of Three". Basically, if you acquire a resource
in the constructor, then you're going to need to write a special copy-
constructor and assignment operator, e.g.:
(Before I begin, I'm going to write a small template in order to help me
default-initialise a dynamically allocated array -- functionality which is
lacking is C++.)
template<class T, unsigned long len>
struct DefInitArray {
T array[len];
DefInitArray() : array() {}
};
Now here's my code:
#include <cstring>
class ArbitraryClass {
protected:
static unsigned long const buflen = 1024;
char * const p_buf;
public:
ArbitraryClass() : p_buf( (new DefInitArray<char,buflen>[1])->array )
{}
ArbitraryClass(const ArbitraryClass &original) : p_buf(new char
[buflen])
{
std::memcpy(p_buf, original.p_buf, sizeof *p_buf * buflen);
}
ArbitraryClass &operator=(const ArbitraryClass &rhs)
{
std::memcpy(p_buf, rhs.p_buf, sizeof *p_buf * buflen);
}
~ArbitraryClass()
{
delete [] p_buf;
}
};
int main()
{
ArbitraryClass a;
ArbitraryClass b( a );
a = b;
}
Expanding on this, I'm going to rename the class to "StringStream", and
then I'm going to add five things to it:
(1) A pointer to the current position in the buffer called "pos".
(2) Code in the constructor, copy-constructor and assignment operator
which correctly sets the value of "pos".
(3) An operator<< in order to write to the stream.
(4) An IsFull() member function to test whether the buffer is full.
(5) A "Print" member function to print the entire buffer contents.
Here's the updated code:
template<class T, unsigned long len>
struct DefInitArray {
T array[len];
DefInitArray() : array() {}
};
#include <cstring>
#include <ostream>
#include <cstdlib>
#include <iostream>
class StringStream {
protected:
static unsigned long const buflen = 1024;
char * const p_buf;
char *pos;
public:
StringStream() :
p_buf( (new DefInitArray<char,buflen>[1])->array ),
pos( p_buf )
{}
StringStream(const StringStream &original) :
p_buf(new char[buflen]),
pos( p_buf + (original.pos - original.p_buf) )
{
std::memcpy(p_buf, original.p_buf, sizeof *p_buf * buflen);
}
StringStream &operator=(const StringStream &rhs)
{
std::memcpy(p_buf, rhs.p_buf, sizeof *p_buf * buflen);
pos = p_buf + (rhs.pos - rhs.p_buf);
}
bool IsFull() const
{
return pos == p_buf + (buflen - 1);
}
StringStream &operator<<( char const c )
{
if( !IsFull() ) *pos++ = c;
return *this;
}
StringStream &operator<<( const char *p )
{
for( ; *p; ++p) *this << *p;
}
void Print( std:
{
out << p_buf << '\n';
}
~StringStream()
{
delete [] p_buf;
}
};
int main()
{
StringStream a;
a << "Hello, my name's Fred";
a.Print( std::cout );
StringStream b( a );
b << ", and I'm a clone!";
b.Print( std::cout );
a = b;
a.Print( std::cout );
std::system("PAUSE");
}
Okay so my code works fine, so what's missing?
What happens if a StringStream object gets re-located in memory (perhaps by
a swap)? If it were to be re-located, its "pos" member would effectively
become corrupt.
So my question is:
What are the Thing To Be Aware Of when storing an object's address
within itself (or the address of a member object, or base class, etc.)?
Secondly:
What exactly do I have to do to my StringStream class in order for it
to survive a relocation? Would I be right in thinking I've to do something
like:
namespace std {
void swap( StringStream&, StringStream& );
}