design question

S

Shea Martin

I have a MyString class:
class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};

Passing func1 a const char *, will result in a deep copy occurring. Because str
is const, the deep copy is not necessary. Obviously I can't have my conversion
constructor do a shallow copy, unless I know the created object is const.

It there a way to differentiate whether a constructor is creating a const object
or not?

i.e.,

class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}
const MyString(const char *str)
{
_buffer = (char*)str;
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};


Obviously this syntax won't work, but you get the idea of what I am trying to
accomplish.

Thanks,

~Shea M.
 
V

Victor Bazarov

Shea said:
I have a MyString class:
class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};

Passing func1 a const char *, will result in a deep copy occurring.
Because str is const, the deep copy is not necessary.

Apparently, a copy is not necessary at all. So, why not just overload
the 'func1' and have the second version accept const char* ?

Obviously I can't
have my conversion constructor do a shallow copy, unless I know the
created object is const.

It there a way to differentiate whether a constructor is creating a
const object or not?
No.

[..]

V
 
D

David Hilsee

Shea Martin said:
I have a MyString class:
class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};

Passing func1 a const char *, will result in a deep copy occurring. Because str
is const, the deep copy is not necessary. Obviously I can't have my conversion
constructor do a shallow copy, unless I know the created object is const.

It there a way to differentiate whether a constructor is creating a const object
or not?

i.e.,

class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}
const MyString(const char *str)
{
_buffer = (char*)str;
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};


Obviously this syntax won't work, but you get the idea of what I am trying to
accomplish.

I think you are misunderstanding what const means. Most objects that exist
in a C++ program are not const. When they are passed to functions, they may
be passed by reference-to-const or pointer-to-const, but that does not mean
that the original object is const. It just means that the function will not
modify the object (at least, not without some casting). Example:

char buffer[] = "foo";
const MyString myStr(buffer);

// if no deep copy is made, then the following makes func1
// display "boo" instead of "foo" (once func1 is made const)
buffer[0] = 'b';

Besides that, there are object lifetime issues to consider. The original
array could have a shorter lifetime than the MyString instance. I think you
should perform a deep copy.
 
S

Shea Martin

Victor said:
Shea said:
I have a MyString class:
class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};

Passing func1 a const char *, will result in a deep copy occurring.
Because str is const, the deep copy is not necessary.


Apparently, a copy is not necessary at all. So, why not just overload
the 'func1' and have the second version accept const char* ?
That is what I currently do (with a couple of shortcuts). This is easy for just
func1(), but a PIA for funcN()...func99(). I was just hoping there was a more
elegant way of doing it.

Thanks,

~S
Obviously I can't
have my conversion constructor do a shallow copy, unless I know the
created object is const.

It there a way to differentiate whether a constructor is creating a
const object or not?

No.

[..]


V
 
S

Shea Martin

David said:
I have a MyString class:
class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};

Passing func1 a const char *, will result in a deep copy occurring.

Because str
is const, the deep copy is not necessary. Obviously I can't have my
conversion

constructor do a shallow copy, unless I know the created object is const.

It there a way to differentiate whether a constructor is creating a const
object

or not?

i.e.,

class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}
const MyString(const char *str)
{
_buffer = (char*)str;
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};


Obviously this syntax won't work, but you get the idea of what I am trying
to

accomplish.


I think you are misunderstanding what const means. Most objects that exist
in a C++ program are not const. When they are passed to functions, they may
be passed by reference-to-const or pointer-to-const, but that does not mean
that the original object is const. It just means that the function will not
modify the object (at least, not without some casting). Example:

char buffer[] = "foo";
const MyString myStr(buffer);

// if no deep copy is made, then the following makes func1
// display "boo" instead of "foo" (once func1 is made const)
buffer[0] = 'b';

Besides that, there are object lifetime issues to consider. The original
array could have a shorter lifetime than the MyString instance. I think you
should perform a deep copy.

I realize the danger of shallow copies, I was just hoping that there might way
to save a deep copy, when I don't need it (without overloading).

~S
 
D

David Hilsee

I realize the danger of shallow copies, I was just hoping that there might way
to save a deep copy, when I don't need it (without overloading).

Are you concerned about performance? For a string class, I would bet that
the overhead of dynamically allocating the array has more of an adverse
impact on performance than the actual copy. You could run some tests to
determine what is the problem. If there is a performance problem with the
dynamic allocation, have you considered using the old trick where a
fixed-width buffer is used for smaller strings and a dynamically-allocated
one is used for larger strings? It might result in cleaner code and more
efficient behavior for the (usually common) smaller strings. Some
std::string implementations (Dinkumware's?) do this.

If you wanted to avoid a deep copy, you could pass an extra "bool
doDeepCopy" to the constructor and cross your fingers that you always
specify the right value, but that could get tricky.
 
A

Alf P. Steinbach

* Shea Martin:
I have a MyString class:

class MyString
{

private:

MyString( MyString const& );
MyString& operator=( MyString const& );

public:
MyString() {}

Uh oh,

Mystring(): buffer_( 0 ) {}

MyString(const char *str)

MyString( char const* str ): buffer_( 0 )
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}

Uh oh,

~MyString() { delete buffer_; }
void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};

Passing func1 a const char *, will result in a deep copy occurring.

You mean, an instantiation, not a deep copy.

Because str is const, the deep copy is not necessary.

You mean, because func1 is your own function where you control the
argument type, an instantiation of MyString is not necessary.

Obviously I can't have my conversion
constructor do a shallow copy, unless I know the created object is const.

You mean, obviously you cannot let your MyString object refer directly to
the client code's char const* pointer unless you know that that character
array will exist for the duration of the MyString object's lifetime, and
furthermore you cannot do things efficiently and be const correct etc.
unless you know that it will also be unchanging during that time.

It there a way to differentiate whether a constructor is creating a const object
or not?

You mean, is there a way to differentiate whether a char const* pointer
passed as actual argument to a MyString constructor points to a character
array that will exist and be unchanging for the duration of the MyString
object?

Yes, you can impose that as a requirement on client code.

But other than that, no.
 
D

Daniel T.

Shea Martin said:
I have a MyString class:
class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};

Passing func1 a const char *, will result in a deep copy occurring. Because
str
is const, the deep copy is not necessary. Obviously I can't have my
conversion
constructor do a shallow copy, unless I know the created object is const.

How about this:

class MyString {
char* _buffer;
public:
// the basic stuff, just to show how it's done...
MyString(): _buffer( 0 ) { }
MyString( const char* c ):
_buffer( c ? new char[strlen( c ) + 1] : 0 ) {
if ( _buffer ) strcpy( _buffer, c );
}
MyString( const MyString& s ):
_buffer( s._buffer ? new char[strlen( s._buffer ) + 1] : 0 ) {
if ( _buffer ) strcpy( _buffer, s._buffer );
}
~MyString() { delete [] _buffer; }
MyString& operator=( const MyString& s ) {
char* c = s._buffer ? new char[strlen( s._buffer ) + 1] : 0;
if ( c ) strcpy( c, s._buffer );
delete [] _buffer;
_buffer = c;
}

// what you seem to be asking about...
static void func1( const char* str ) {
printf( "str is %s\n", str );
}

static void func1( const MyString& str ) {
printf( "str is %s\n", str._buffer );
}
};

A couple of points. 'func1' is made static because it doesn't use the
'this' pointer. The first version 'func1(const char*)' will be called
for string literals or other char pointers, while the second version
will be called for MyString objects.
It there a way to differentiate whether a constructor is creating a const
object or not?
No.

Obviously this syntax won't work, but you get the idea of what I am trying to
accomplish.

You can't do what you are trying to accomplish (assuming I get it.)
There is no way to know for sure if a particular 'const char*' is
pointing to a string literal or not. It may be pointing to a auto char[]
or a dynamically allocated block...
 
A

Anil Mamede

I have a MyString class:
class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

void func1(const char* str)
{
printf("str is %s\n", str);
}



private:
char *_buffer;
};

Passing func1 a const char *, will result in a deep copy occurring. Because str
is const, the deep copy is not necessary. Obviously I can't have my conversion
constructor do a shallow copy, unless I know the created object is const.


Anil Mamede
 
I

Ioannis Vranos

Shea said:
It there a way to differentiate whether a constructor is creating a
const object or not?

i.e.,

class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}
const MyString(const char *str)
{
_buffer = (char*)str;
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};


Obviously this syntax won't work, but you get the idea of what I am
trying to accomplish.


There are many ways. E.g.

class MyString
{
char *buffer;
const char *buffer;
bool shallowed;


public:

MyString(const char *str, bool shallow=false)
{
shallowed=shallow;

if(shallow)
const_buffer=str;

else
{
buffer=new new char[strlen(str)+1];
// ...
}
}

// ...
};



However the whole design doesn't look good. You have func1() as a member
function, etc.


Not to mention why you bother to define your string class, while there
is std::string.






Regards,

Ioannis Vranos

http://www23.brinkster.com/noicys
 
I

Ioannis Vranos

Ioannis Vranos wrote:


Some stupid mistakes fixed:


There are many ways. E.g.

class MyString
{
char *buffer;
const char *const_buffer;
bool shallowed;


public:

MyString(const char *str, bool shallow=false)
{
shallowed=shallow;

if(shallow)
const_buffer=str;

else
{
buffer=new char[strlen(str)+1];
// ...
}
}

// ...
};


However the whole design doesn't look good. You have func1() as a member
function, etc.


Not to mention why you bother to define your string class, while there
is std::string.






Regards,

Ioannis Vranos

http://www23.brinkster.com/noicys
 
R

Rich Grise

Shea said:
I have a MyString class: ....
Passing func1 a const char *, will result in a deep copy occurring.
Because str is const, the deep copy is not necessary. Obviously I can't
have my conversion constructor do a shallow copy, unless I know the
created object is const.

Pardon me for butting in, but I've scanned the first four C++ glossaries at
http://www.google.com/search?hl=en&ie=ISO-8859-1&q=+c++++glossary&btnG=Google+Search

and haven't found "deep copy". Or "deep" anything, for that matter. What
does it mean?

Thanks,
Rich
 
D

Daniel T.

Rich Grise said:
Pardon me for butting in, but I've scanned the first four C++ glossaries at
http://www.google.com/search?hl=en&ie=ISO-8859-1&q=+c++++glossary&btnG
=Google+Search

and haven't found "deep copy". Or "deep" anything, for that matter. What
does it mean?

class Shallow {
char* buf;
public:
Shallow( char* b ): buf( b ) { }
Shallow( const Shallow& other ): buf( other.buf ) { }
~Shallow() {
// the block of memory pointed to by 'buf' might be shared,
// so we can't risk deleting it
}
Shallow& operator=( const Shallow& other ) {
buf = other.buf;
return *this;
}
};

class Deep {
char* buf;
public:
Deep( const char* b ): buf( 0 ) {
if ( b ) {
buf = new char[strlen( b ) + 1];
strcpy( buf, b );
}
}
Deep( const Deep& other ): buf( 0 ) {
if ( other.buf ) {
buf = new char[strlen( b ) + 1];
strcpy( buf, other.buf );
}
}
~Deep() {
delete [] buf; // we can delete here because we know that
// no other code is using the memory
}
Deep& operator=( const Deep& other ) {
char* temp = 0;
if ( other.buf ) {
temp = new char[strlen( b ) + 1];
strcpy( temp, other.buf );
}
swap( buf, temp );
delete [] temp;
return *this;
}
};

(Quick note, there is a lot of needless duplication in 'Deep' above that
was left in so that people less skilled in the language would find it
easier to follow. Make it an exorcise to remove as much duplication as
possible.)

Examine the two classes above, when you understand them, you will
understand the difference between shallow and deep copy...
 
S

Shea Martin

David said:
Are you concerned about performance? For a string class, I would bet that
the overhead of dynamically allocating the array has more of an adverse
impact on performance than the actual copy. You could run some tests to
determine what is the problem. If there is a performance problem with the
dynamic allocation, have you considered using the old trick where a
fixed-width buffer is used for smaller strings and a dynamically-allocated
one is used for larger strings? It might result in cleaner code and more
efficient behavior for the (usually common) smaller strings. Some
std::string implementations (Dinkumware's?) do this.

If you wanted to avoid a deep copy, you could pass an extra "bool
doDeepCopy" to the constructor and cross your fingers that you always
specify the right value, but that could get tricky.
I use a plus or minus system: when allocating I always allocate the amount I
need plus a little more. I also don't bother to shrink my buffer unless the
length of the string varies from the buffer size by a certain threshold.

I realize that the allocate is much slower than the copy. That is why a shallow
copy in certain situations is would avoid an array allocation all together.

Actually what I do now, is have a private method called setBuffer. When a const
char is passed, and I know I won't be altering its contents, I assign the const
char array to the char array member of the string class, and call the same
method using the newly created string. While this approach still requires
overloading, at least it lets me put all my logic in one method instead of doing
copy paste. I still don't really like this method, as it makes me feel dirty.

~S
 
S

Shea Martin

Alf said:
* Shea Martin:
I have a MyString class:

class MyString
{


private:

MyString( MyString const& );
MyString& operator=( MyString const& );


public:
MyString() {}


Uh oh,

Mystring(): buffer_( 0 ) {}


MyString(const char *str)


MyString( char const* str ): buffer_( 0 )

{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}


Uh oh,

~MyString() { delete buffer_; }

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};

Passing func1 a const char *, will result in a deep copy occurring.


You mean, an instantiation, not a deep copy.


Because str is const, the deep copy is not necessary.


You mean, because func1 is your own function where you control the
argument type, an instantiation of MyString is not necessary.


Obviously I can't have my conversion
constructor do a shallow copy, unless I know the created object is const.


You mean, obviously you cannot let your MyString object refer directly to
the client code's char const* pointer unless you know that that character
array will exist for the duration of the MyString object's lifetime, and
furthermore you cannot do things efficiently and be const correct etc.
unless you know that it will also be unchanging during that time.

Because func1 is my code, I know that I will only be using the const char * for
the duration of the func1 call, and as read-only. So I in a single threaded
environment, I can consider the call to func1 atomic, so I know I will be OK.
Basically I was looking for a way to cheat, and the resounding answer seems to
be don't cheat.

~S
 
S

Shea Martin

Ioannis said:
Shea said:
It there a way to differentiate whether a constructor is creating a
const object or not?

i.e.,

class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}
const MyString(const char *str)
{
_buffer = (char*)str;
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};


Obviously this syntax won't work, but you get the idea of what I am
trying to accomplish.



There are many ways. E.g.

class MyString
{
char *buffer;
const char *buffer;
bool shallowed;


public:

MyString(const char *str, bool shallow=false)
{
shallowed=shallow;

if(shallow)
const_buffer=str;

else
{
buffer=new new char[strlen(str)+1];
// ...
}
}

// ...
};



However the whole design doesn't look good. You have func1() as a member
function, etc.


Not to mention why you bother to define your string class, while there
is std::string.

My working environment doesn't like the use of templates. Plus std::string is
pretty limited. For a good example of a useful string class, look at Java's
string or Qt's QString. I prefere to use char arrays over std::string.

~S
 
D

Daniel T.

Shea Martin said:
I have a MyString class:
class MyString
{
public:
MyString() {}
MyString(const char *str)
{
_buffer = new char[strlen(str)+1];
strcpy(_buffer, str);
}

void func1(const MyString &str)
{
printf("str is %s\n", str._buffer);
}

private:
char *_buffer;
};

Passing func1 a const char *, will result in a deep copy occurring. Because
str
is const, the deep copy is not necessary. Obviously I can't have my
conversion
constructor do a shallow copy, unless I know the created object is const.

It there a way to differentiate whether a constructor is creating a const
object
or not?

Here's an idea:

ConstString {
const char* _data;
public:
ConstString( const char* d ): _data( d ) { }
// only include methods that don't change the guts of _data
std::string modifiable() const { return string( _data ); }
};

The above can be used with string literals:

ConstString s( "Hello" );

If you want to change the value, you will need to convert it first:

std::string ss( s.modifiable() );
ss.append( " world" );
 

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

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,020
Latest member
GenesisGai

Latest Threads

Top