473,383 Members | 1,788 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,383 software developers and data experts.

Rule of three and so forth...

(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
5 1382
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
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
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
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
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 thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

38
by: BORT | last post by:
Please forgive me if this is TOO newbie-ish. I am toying with the idea of teaching my ten year old a little about programming. I started my search with something like "best FREE programming...
145
by: Sidney Cadot | last post by:
Hi all, In a discussion with Tak-Shing Chan the question came up whether the as-if rule can cover I/O functions. Basically, he maintains it can, and I think it doesn't. Consider two...
5
by: pembed2003 | last post by:
Hi all, I am reading the book "C How to Program" and in the chapter where it discuss scope rule, it says there are four scopes for a variable: function scope file scope block scope...
50
by: diffuser78 | last post by:
I have just started to learn python. Some said that its slow. Can somebody pin point the issue. Thans
3
by: comp.lang.php | last post by:
I have a counter that evokes the "Three Strikes You're Out" rule.. if you make more than N mistakes it auto-resets to avoid flooding $_SESSION with attempt after attempt, etc. However, the...
9
by: utab | last post by:
Dear all, How do experienced programmers using this group use the rule of thumb,namely copy ctor, assignment operator, and dtor. example if the class does not need them dont use and define...
30
by: Paul H | last post by:
I seem to end up with loads of append and update queries just because it's quick and easy to build queries or make a new ones based on an existing query. But I end up with loads of queries with...
42
by: John Doty | last post by:
I realized that I have a little job on the table that is a fine test of the Python versus Standard Forth code availability and reusability issue. Note that I have little experience with either...
1
by: CloudSolutions | last post by:
Introduction: For many beginners and individual users, requiring a credit card and email registration may pose a barrier when starting to use cloud servers. However, some cloud server providers now...
0
by: Faith0G | last post by:
I am starting a new it consulting business and it's been a while since I setup a new website. Is wordpress still the best web based software for hosting a 5 page website? The webpages will be...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 3 Apr 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome former...
0
by: ryjfgjl | last post by:
In our work, we often need to import Excel data into databases (such as MySQL, SQL Server, Oracle) for data analysis and processing. Usually, we use database tools like Navicat or the Excel import...
0
by: aa123db | last post by:
Variable and constants Use var or let for variables and const fror constants. Var foo ='bar'; Let foo ='bar';const baz ='bar'; Functions function $name$ ($parameters$) { } ...
0
by: ryjfgjl | last post by:
If we have dozens or hundreds of excel to import into the database, if we use the excel import function provided by database editors such as navicat, it will be extremely tedious and time-consuming...
0
by: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.