AnyClass

J

JKop

I've written two files:

anyclass.cpp
anyclass.hpp


Within is the definition of the class "AnyClass". It's used for testing
purposes and for playing around, having some fun.

What the class does is (optionally) print text whenever an object of it is
created, destroyed, assigned to.

The constructor works as so:

AnyClass blah("blah");


You supply it with a name. This name will be copied into its internal
storage. All text which is printed will refer to the object by name. If you
do the following:

AnyClass poo = blah;

Then the object "poo" will have the name "Copy of blah".


Here's its public members:

object_counter : a static read-only variable of type "unsigned" which holds
the amount of objects of "AnyClass" currently in existence.

GetName() : returns a pointer to the string which contains the name of the
object.

to_play_with : just a non-const member variable of type "unsigned" to play
around with.


Here's an example usage:


#include "anyclass.hpp"

int main()
{
AnyClass const &blah = AnyClass("no-name");

std::cout << "\nHaHa\n";
}


This program will output the following:

AnyClass Constructor for: no-name

HaHa
AnyClass Destructor for: no-name


Which shows that the temporary in the above is valid until the end of the
function.

If you don't want the class to print out at all, then do the following:

#define ANYCLASS_NO_OUTPUT
#include "anyclass.hpp"


but unfortunately you'll also have to edit the ".cpp" file also to achieve
this.


Here it is. . .

//anyclass.hpp

#ifndef INCLUDE_ANYCLASS_HPP
#define INCLUDE_ANYCLASS_HPP

#ifndef ANYCLASS_NO_OUTPUT
#include <iostream>
#endif

#include <cstddef>
#include <cstring>

class AnyClass
{
public:

static unsigned const &object_counter;

unsigned to_play_with;


const char* GetName() const
{
return name;
}


private:

static unsigned object_counter_prv;
char* name;

public:

AnyClass(const char* const in_name, unsigned const in_to_play_with = 0)
: to_play_with(in_to_play_with)
{

std::size_t length = std::strlen( in_name );

name = new char[length += 1];

memcpy(name, in_name, length);

++object_counter_prv;

#ifndef ANYCLASS_NO_OUTPUT
std::cout << " AnyClass Constructor for: " << name <<
'\n';
#endif

}


AnyClass(AnyClass const &original) : to_play_with
(original.to_play_with)
{
std::size_t length = std::strlen( original.name );

name = new char[length += 9]; //9 = 8 + 1 (1 for the null
character)

memcpy( name, "Copy of " , 8 ); // . . .waste of a null
character

memcpy( &name[8], original.name, length -= 9 ); //Take the 9
back off

name[length += 8] = '\0';

++object_counter_prv;

#ifndef ANYCLASS_NO_OUTPUT
std::cout << "AnyClass Copy Constructor for: " << name << '\n';
#endif
}

AnyClass& operator=(AnyClass const &other)
{
//NB: There is no name change whatsoever

to_play_with = other.to_play_with;

#ifndef ANYCLASS_NO_OUTPUT
std::cout << " AnyClass Assignment Operator: " << name << "
= " << other.name << '\n';
#endif

return *this;
}

~AnyClass()
{
#ifndef ANYCLASS_NO_OUTPUT
std::cout << " AnyClass Destructor for: " << name <<
'\n';
#endif

delete [] name;
--object_counter_prv;
}
};


#endif



//anyclass.cpp

#include "anyclass.hpp"

unsigned AnyClass::eek:bject_counter_prv = 0;
unsigned const &AnyClass::eek:bject_counter = AnyClass::eek:bject_counter_prv;




Any comments, questions, suggestions welcomed.


-JKop
 
D

David Harmon

std::size_t length = std::strlen( original.name );

name = new char[length += 9]; //9 = 8 + 1 (1 for the null
character)

memcpy( name, "Copy of " , 8 ); // . . .waste of a null
character

memcpy( &name[8], original.name, length -= 9 ); //Take the 9
back off

In my opinion, you should use std::string and omit all that error-prone
char* hacking.
 
J

JKop

David Harmon posted:
On Tue, 12 Oct 2004 13:34:44 GMT in comp.lang.c++, JKop
std::size_t length = std::strlen( original.name );

name = new char[length += 9]; //9 = 8 + 1 (1 for the
null character)

memcpy( name, "Copy of " , 8 ); // . . .waste of a null
character

memcpy( &name[8], original.name, length -= 9 ); //Take
the 9 back off

In my opinion, you should use std::string and omit all that error-prone
char* hacking.

I strongly disagree.

There is nothing error-prone about the above, nor is there any "hacking".
It's just good ol' C++.

In my own opinion, it's a disgrace to use "std::string" and the like for
simple uses like the above. Does no-one believe in efficiency anymore?

I will although admit that it takes a bit of thinking to make sure you put
in the right array indexes, eg. "&name[8]", but I actually enjoy that and I
end up with much more efficient code.

I'll say that there are however great uses for "std::string". I use in when
I'm doing *extreme* string manipulation, especially where I would have to
resize the buffer were I to do it myself. In the cases, it's preferrable to
use simple "std::string" members than to spend an hour messing with chars.


-JKop
 
J

Jerry Coffin

JKop said:
David Harmon posted:

[ ... ]
I strongly disagree.

There is nothing error-prone about the above, nor is there any "hacking".
It's just good ol' C++.

In my own opinion, it's a disgrace to use "std::string" and the like for
simple uses like the above. Does no-one believe in efficiency anymore?

Quite a few of us do -- and we realize (for example) that doing the
job with std::string will often be substantially MORE efficient than
using C string functions.

Just for example, your code contains a call to strlen, which is an
O(N) function since it has to search through the entire string to find
the end. By contrast, an std::string object normally stores the
current length as a member, meaning that std::string::length() is a
trivial O(1) function.

As such, your code's speed depends (heavily) on the length of
original.name. An implementation using std::string instead can remove
this depency, and retain essentially the same (or better) efficiency
in other areas as well. There will inevitably be a length of
original.name beyond which std::string is more efficient than your
code -- and that length may easily be 0.
I will although admit that it takes a bit of thinking to make sure you put
in the right array indexes, eg. "&name[8]", but I actually enjoy that and I
end up with much more efficient code.

The name for that kind of code is "fragile." If you insist on writing
something like this, a bare minimum of decency would be code something
like:

static char cpy[] = "Copy of ";
static size_t len = sizeof(cpy);

memcpy(name, cpy, len-1);
memcpy(name+len-1, original.name, length-=len);

This way, you don't have a "magic" number like 8 in the code, instead
using a value whose derivation is obvious. Better still, if (for
example) somebody decided to translate the code to their choice of
languages other than English, where "copy of" would almost certainly
be a different size, the size would automatically change with the
change in string length.

IMO, all of this remains essentially pointless: given a halfway
reasonable implementation of the standard library, I'd expect
std::string to be right in the same general ballpark for speed as
doing this manually. To test this theory, I created a modified version
of your class using std::string:

//anyclass.hpp

#ifndef INCLUDE_ANYCLASS_HPP
#define INCLUDE_ANYCLASS_HPP

#ifndef ANYCLASS_NO_OUTPUT
#include <iostream>
#endif

#include <string>

class AnyClass
{
public:
static unsigned const &object_counter;

unsigned to_play_with;

std::string const &GetName() const
{
return name;
}


private:

static unsigned object_counter_prv;
std::string name;

public:

AnyClass(std::string const &in_name, unsigned in_to_play_with =
0)
: to_play_with(in_to_play_with), name(in_name)
{
++object_counter_prv;

#ifndef ANYCLASS_NO_OUTPUT
std::cout << " AnyClass Constructor for: " << name <<
'\n';
#endif

}


AnyClass(AnyClass const &original) :
to_play_with(original.to_play_with)
{
name = std::string("Copy of ") + original.name;
++object_counter_prv;

#ifndef ANYCLASS_NO_OUTPUT
std::cout << "AnyClass Copy Constructor for: " << name <<
'\n';
#endif
}

AnyClass& operator=(AnyClass const &other)
{
//NB: There is no name change whatsoever

to_play_with = other.to_play_with;

#ifndef ANYCLASS_NO_OUTPUT
std::cout << " AnyClass Assignment Operator: " <<
name << " = " << other.name << '\n';
#endif

return *this;
}

~AnyClass()
{
#ifndef ANYCLASS_NO_OUTPUT
std::cout << " AnyClass Destructor for: " << name <<
'\n';
#endif
--object_counter_prv;
}
};

#endif

and then I whipped together a quick test harness:

#define ANYCLASS_NO_OUTPUT

#include <time.h>

#ifdef USESTRING
#include "anyclass.hpp"
#else
#include "anyclass1.hpp"
#endif

#include <iostream>
int main() {

const int num = 5000;

AnyClass *x[num];

clock_t start = clock();
x[0] = new AnyClass("X");
for (int i=1; i<num; i++)
x = new AnyClass(*x[i-1]);
clock_t overall = clock()-start;

#ifdef USESTRING
std::cout << "length: " << x[num-1]->GetName().length();
#else
std::cout << "length: " << strlen(x[num-1]->GetName());
#endif

std::cout << "\nTime: " << overall/(double)CLOCKS_PER_SEC <<
" seconds\n";
return 0;
}

I compiled the code with VC++ 7.1 using:

cl /O2b2 /G7ry any.cpp anyclass.cpp

and:

cl /DUSESTRING /O2b2 /G7ry any.cpp anyclass.cpp

and ran both versions. On my particular machine, both versions claimed
to run in .312 seconds. It's possible (probable, in reality) that if
you ran them often enough that you'd at least ocassionally see a
difference in speed (on the order of a single clock tick worth, I'd
guess). With a lot of runs and careful statistical analysis, you might
even even find a statistically significant difference between them --
but without more testing, it's hard to even guess which one would be
likely to win -- std::string might be about as likely to be faster as
slower. In any case, I think that's more or less irrelevant: there's
no real room for question that the code using std::string is a LOT
simpler, and the code using raw char *'s clearly is not a LOT faster,
which is what would be needed to justify its own existence.

I'd also note that using raw pointers makes exception safety
considerably more difficult to provide.
 
J

JKop

Jerry Coffin posted:
JKop said:
David Harmon posted:

[ ... ]
I strongly disagree.

There is nothing error-prone about the above, nor is there any
"hacking". It's just good ol' C++.

In my own opinion, it's a disgrace to use "std::string" and the like
for simple uses like the above. Does no-one believe in efficiency
anymore?

Quite a few of us do -- and we realize (for example) that doing the
job with std::string will often be substantially MORE efficient than
using C string functions.

Just for example, your code contains a call to strlen, which is an
O(N) function since it has to search through the entire string to find
the end. By contrast, an std::string object normally stores the
current length as a member, meaning that std::string::length() is a
trivial O(1) function.

As such, your code's speed depends (heavily) on the length of
original.name. An implementation using std::string instead can remove
this depency, and retain essentially the same (or better) efficiency
in other areas as well. There will inevitably be a length of
original.name beyond which std::string is more efficient than your
code -- and that length may easily be 0.
I will although admit that it takes a bit of thinking to make sure you
put in the right array indexes, eg. "&name[8]", but I actually enjoy
that and I end up with much more efficient code.

The name for that kind of code is "fragile." If you insist on writing
something like this, a bare minimum of decency would be code something
like:

static char cpy[] = "Copy of ";
static size_t len = sizeof(cpy);

memcpy(name, cpy, len-1);
memcpy(name+len-1, original.name, length-=len);

This way, you don't have a "magic" number like 8 in the code, instead
using a value whose derivation is obvious. Better still, if (for
example) somebody decided to translate the code to their choice of
languages other than English, where "copy of" would almost certainly
be a different size, the size would automatically change with the
change in string length.

IMO, all of this remains essentially pointless: given a halfway
reasonable implementation of the standard library, I'd expect
std::string to be right in the same general ballpark for speed as
doing this manually. To test this theory, I created a modified version
of your class using std::string:

//anyclass.hpp

#ifndef INCLUDE_ANYCLASS_HPP
#define INCLUDE_ANYCLASS_HPP

#ifndef ANYCLASS_NO_OUTPUT
#include <iostream>
#endif

#include <string>

class AnyClass
{
public:
static unsigned const &object_counter;

unsigned to_play_with;

std::string const &GetName() const
{
return name;
}


private:

static unsigned object_counter_prv;
std::string name;

public:

AnyClass(std::string const &in_name, unsigned in_to_play_with =
0)
: to_play_with(in_to_play_with), name(in_name)
{
++object_counter_prv;

#ifndef ANYCLASS_NO_OUTPUT
std::cout << " AnyClass Constructor for: " << name <<
'\n';
#endif

}


AnyClass(AnyClass const &original) :
to_play_with(original.to_play_with)
{
name = std::string("Copy of ") + original.name;
++object_counter_prv;

#ifndef ANYCLASS_NO_OUTPUT
std::cout << "AnyClass Copy Constructor for: " << name <<
'\n';
#endif
}

AnyClass& operator=(AnyClass const &other)
{
//NB: There is no name change whatsoever

to_play_with = other.to_play_with;

#ifndef ANYCLASS_NO_OUTPUT
std::cout << " AnyClass Assignment Operator: " <<
name << " = " << other.name << '\n';
#endif

return *this;
}

~AnyClass()
{
#ifndef ANYCLASS_NO_OUTPUT
std::cout << " AnyClass Destructor for: " << name <<
'\n';
#endif
--object_counter_prv;
}
};

#endif

and then I whipped together a quick test harness:

#define ANYCLASS_NO_OUTPUT

#include <time.h>

#ifdef USESTRING
#include "anyclass.hpp"
#else
#include "anyclass1.hpp"
#endif

#include <iostream>
int main() {

const int num = 5000;

AnyClass *x[num];

clock_t start = clock();
x[0] = new AnyClass("X");
for (int i=1; i<num; i++)
x = new AnyClass(*x[i-1]);
clock_t overall = clock()-start;

#ifdef USESTRING
std::cout << "length: " << x[num-1]->GetName().length();
#else
std::cout << "length: " << strlen(x[num-1]->GetName());
#endif

std::cout << "\nTime: " << overall/(double)CLOCKS_PER_SEC <<
" seconds\n";
return 0;
}

I compiled the code with VC++ 7.1 using:

cl /O2b2 /G7ry any.cpp anyclass.cpp

and:

cl /DUSESTRING /O2b2 /G7ry any.cpp anyclass.cpp

and ran both versions. On my particular machine, both versions claimed
to run in .312 seconds. It's possible (probable, in reality) that if
you ran them often enough that you'd at least ocassionally see a
difference in speed (on the order of a single clock tick worth, I'd
guess). With a lot of runs and careful statistical analysis, you might
even even find a statistically significant difference between them --
but without more testing, it's hard to even guess which one would be
likely to win -- std::string might be about as likely to be faster as
slower. In any case, I think that's more or less irrelevant: there's
no real room for question that the code using std::string is a LOT
simpler, and the code using raw char *'s clearly is not a LOT faster,
which is what would be needed to justify its own existence.

I'd also note that using raw pointers makes exception safety
considerably more difficult to provide.



I think this newsgroup has the best community of all I've been to!

Thanks for working all that out, Gerry.


You know that template "getcount" (or something like that) that tells you
the length of an array... well I found another use for it:

getcount("Copy of ");


:-D



-JKop
 
J

Jerry Coffin

[ ... ]
Thanks for working all that out, Gerry.

You're certainly welcome, but my name is Jerry.
You know that template "getcount" (or something like that) that tells you
the length of an array... well I found another use for it:

getcount("Copy of ");

Actually, if you insist on using memcpy (or memmove, etc.) that's
_not_ what you really want. The argument to memcpy specifies a number
of _bytes_ to move, _not_ the number of elements.

In this particular case, with a string of char's, the two will be the
same because the C and C++ standards specify sizeof(char) is always 1.

Using the number of elements will lead to a problem, however, if the
type of string changes -- the obvious choice being to change it to
something like:

wchar_t cpy[] = L"Copy of";

in which case using sizeof still yields the correct size, but using
the number of elements will typically yield one half or one quarter
the correct size.

The bottom line is pretty simple: memcpy works in bytes, so you need
to specify its arguments in bytes. You'd use the number of elements if
you were using a function (or template) that works in the number of
elements, such as some in the C++ standard libary (e.g. std::fill_n --
std::copy uses iterators to specify the starting and ending points).
 
J

JKop

Jerry Coffin posted:
[ ... ]
Thanks for working all that out, Gerry.

You're certainly welcome, but my name is Jerry.


Sorry about that, force of habit (I live in Ireland and it's spelled
"Gerry" over here).

You know that template "getcount" (or something like that) that tells
you the length of an array... well I found another use for it:

getcount("Copy of ");

Actually, if you insist on using memcpy (or memmove, etc.) that's
_not_ what you really want. The argument to memcpy specifies a number
of _bytes_ to move, _not_ the number of elements.

In this particular case, with a string of char's, the two will be the
same because the C and C++ standards specify sizeof(char) is always 1.

Using the number of elements will lead to a problem, however, if the
type of string changes -- the obvious choice being to change it to
something like:

wchar_t cpy[] = L"Copy of";

in which case using sizeof still yields the correct size, but using
the number of elements will typically yield one half or one quarter
the correct size.

The bottom line is pretty simple: memcpy works in bytes, so you need
to specify its arguments in bytes. You'd use the number of elements if
you were using a function (or template) that works in the number of
elements, such as some in the C++ standard libary (e.g. std::fill_n --
std::copy uses iterators to specify the starting and ending points).

memcpy( y, x, getcount(array) * sizeof(array[0]) );


-JKop
 
D

David Rubin

[snip]

Given some version of class 'AnyClass' with a contructor like

AnyClass(const std::string& name, unsigned value = 0);
or
AnyClass(const char *name, unsigned value = 0);

....
and then I whipped together a quick test harness:

#define ANYCLASS_NO_OUTPUT

#include <time.h>

#ifdef USESTRING
#include "anyclass.hpp"
#else
#include "anyclass1.hpp"
#endif

#include <iostream>
int main() {

const int num = 5000;

AnyClass *x[num];

clock_t start = clock();
x[0] = new AnyClass("X");
for (int i=1; i<num; i++)
x = new AnyClass(*x[i-1]);
clock_t overall = clock()-start;

#ifdef USESTRING
std::cout << "length: " << x[num-1]->GetName().length();
#else
std::cout << "length: " << strlen(x[num-1]->GetName());
#endif

std::cout << "\nTime: " << overall/(double)CLOCKS_PER_SEC <<
" seconds\n";
return 0;
}

I compiled the code with VC++ 7.1 using:

cl /O2b2 /G7ry any.cpp anyclass.cpp

and:

cl /DUSESTRING /O2b2 /G7ry any.cpp anyclass.cpp

and ran both versions. On my particular machine, both versions claimed
to run in .312 seconds. It's possible (probable, in reality) that if
you ran them often enough that you'd at least ocassionally see a
difference in speed (on the order of a single clock tick worth, I'd
guess). With a lot of runs and careful statistical analysis, you might
even even find a statistically significant difference between them --
but without more testing, it's hard to even guess which one would be
likely to win -- std::string might be about as likely to be faster as
slower. In any case, I think that's more or less irrelevant: there's
no real room for question that the code using std::string is a LOT
simpler, and the code using raw char *'s clearly is not a LOT faster,
which is what would be needed to justify its own existence.


What you want to do is something more like this:

enum {
NUM_ITERATIONS = 5000,
GROW_LENGTH = 131
};
int length = GROW_LENGTH;
AnyClass x("");
for (int i = 0; i < NUM_ITERATIONS; ++i) {
std::string name(length, '#');
const char *NAME = name.c_str();

clock_t start = std::clock();
#ifdef USESTRING
AnyClass a(name);
#else
AnyClass a(NAME);
#endif
AnyClass b(a);
x = a;
clock_t totalTime = std::clock() - start;
std::cout << "length = " << length << ", "
<< "time = " << totalTime
<< std::endl;
length += GROW_LENGTH;
}

Perhaps you will see some more deviation in the results. /david
 
J

Jerry Coffin

[ ... ]
The bottom line is pretty simple: memcpy works in bytes, so you need
to specify its arguments in bytes. You'd use the number of elements if
you were using a function (or template) that works in the number of
elements, such as some in the C++ standard libary (e.g. std::fill_n --
std::copy uses iterators to specify the starting and ending points).

memcpy( y, x, getcount(array) * sizeof(array[0]) );

IMO, this is just a roundabout way of getting back to where we started
-- getcount is normally defined something like:

#define getcount(array) (sizeof(array)/sizeof(array[0]))

So you're doing:

sizeof(array)/sizeof(array[0]) * sizeof(array[0])

and the sizeof(array[0])'s cancel, giving sizeof(array).

The bottom line is that if you intend to copy the entire array, you
might as well just specify "sizeof(array)" and be done with it.
 
J

JKop

#define getcount(array) (sizeof(array)/sizeof(array[0]))


#include <cstddef>

template<class T, std::size_t amount_elements>
inline std::size_t getcount( T (&array)[amount_elements] )
{
return amount_elements;
}


-JKop
 
R

Richard Herring

Jerry said:
[ ... ]
The bottom line is pretty simple: memcpy works in bytes, so you need
to specify its arguments in bytes. You'd use the number of elements if
you were using a function (or template) that works in the number of
elements, such as some in the C++ standard libary (e.g. std::fill_n --
std::copy uses iterators to specify the starting and ending points).

memcpy( y, x, getcount(array) * sizeof(array[0]) );

IMO, this is just a roundabout way of getting back to where we started
-- getcount is normally defined something like:

#define getcount(array) (sizeof(array)/sizeof(array[0]))

So you're doing:

sizeof(array)/sizeof(array[0]) * sizeof(array[0])

and the sizeof(array[0])'s cancel, giving sizeof(array).

The bottom line is that if you intend to copy the entire array, you
might as well just specify "sizeof(array)" and be done with it.
Except that if getcount() is a template function rather than a macro,
you can use template techniques to check that its argument really is an
array and not something that's accidentally decayed to a pointer.
 
J

JKop

Except that if getcount() is a template function rather than a macro,
you can use template techniques to check that its argument really is an
array and not something that's accidentally decayed to a pointer.


I like!


-JKop
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top