By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
446,171 Members | 963 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 446,171 IT Pros & Developers. It's quick & easy.

Rule of three and so forth...

P: n/a
(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::ostream &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& );
}
--

Frederick Gotham
Jun 28 '06 #1
Share this Question
Share on Google+
5 Replies


P: n/a
Frederick Gotham posted:

StringStream &operator<<( const char *p )
{
for( ; *p; ++p) *this << *p;
}

Wups! I'm missing:

return *this;
Does anyone else think it's RIDICULOUS that g++ doesn't catch that error?!
--

Frederick Gotham
Jun 28 '06 #2

P: n/a
Hello,

Frederick Gotham wrote:
Wups! I'm missing:

return *this;
Does anyone else think it's RIDICULOUS that g++ doesn't catch that
error?!

If you put warnings on with -Wall it will give notice.

Bernd Strieder

Jun 28 '06 #3

P: n/a
Hello,

Frederick Gotham wrote:
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.
A swap is by default defined using copy c'tor and assignments, that
should not be critical. A specialized variant of swap will use swap on
the data members of the class. Either way by swapping the buffer and
the pos will be swapped, and everything is fine. So you must be
thinking about relocation by moving raw memory, which leads immediately
to undefined behaviour on non-POD data types, no surprise, no need to
consider this.

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.)?
Just the rule of three. Every sensible way of relocation should be
possible using the three from the rule of three. Other code with direct
access to the members could do it its own way. All relocating code has
the responsibility to do it right, or you miss the invariants of the
class and it will fail.


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& );
}


Swap is not ciritical, as said before, can be done right. Logically, a
stream class is not a candidate for relocation, anyway.

Bernd Strieder

Jun 28 '06 #4

P: n/a
Bernd Strieder posted:
Hello,

Frederick Gotham wrote:
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.


A swap is by default defined using copy c'tor and assignments, that
should not be critical.

I'm surprised to hear that! I thought (and hoped) that it would be more
efficient, something like:

#include <cstring>

template<class T>
void swap( T &a, T &b )
{
unsigned char buffer[ sizeof(T) ];

std::memcpy( buffer, &a, sizeof(T) );

std::memcpy( &a, &b, sizeof(T) );

std::memcpy( &b, buffer, sizeof(T) );
}
It's for this reason that I thought it was the class implementor's own
responsibility to implement their own swap algorithm if the object stores
its own address within itself.

I might do so anyway, for sake of efficiency.

How might I define it? Should I define it within the std namespace? How
does the following look:

template<class T, unsigned long len>
struct DefInitArray {

T array[len];

DefInitArray() : array() {}

};

#include <cstring>
#include <ostream>
#include <cstdlib>
#include <iostream>

class StringStream;

namespace std { void swap(StringStream&, StringStream&); }

class StringStream {
protected:

static unsigned long const buflen = 1024;

char * const p_buf;

char *pos;

public:

friend void std::swap(StringStream&,StringStream&);

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);

return *this;
}

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;

return *this;
}

void Print( std::ostream &out ) const
{
out << p_buf << '\n';
}

~StringStream()
{
delete [] p_buf;
}

};
namespace std {

void swap( StringStream &a, StringStream &b )
{
if ( &a == &b ) return; /* Is this check redundant? */

if ( &b > &a )
{
b.pos -= &b - &a;
a.pos += &b - &a;
}
else
{
a.pos -= &a - &b;
b.pos += &a - &b;
}

unsigned char temp[ StringStream::buflen ];

std::memcpy( temp, a.p_buf, StringStream::buflen );
std::memcpy( a.p_buf, b.p_buf, StringStream::buflen );
std::memcpy( b.p_buf, temp, StringStream::buflen );
}

}

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::swap( a, b );
std::system("PAUSE");
}

--

Frederick Gotham
Jun 28 '06 #5

P: n/a
On Wed, 28 Jun 2006 11:04:56 GMT, Frederick Gotham
<fg*******@SPAM.com> wrote:
(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.:
Not really ...

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);
} ....};


Your StringStream class is a typical example for a class that should
be non-copyable (see also std::stingstream). Just make your copy
constructor and operator= private and leave them unimplemented.

Best wishes,
Roland Pibinger
Jun 28 '06 #6

This discussion thread is closed

Replies have been disabled for this discussion.