no-fail swap for char []'s

D

Dennis Jones

Hi all,

1) Let's say you have two char []'s of the same size. How would you write a
no-fail swap method for them? For example:

class Test
{
char s[10];
void swap( Test &rhs )
{
// is it possible to swap the data member 's' with the no-fail
guarantee?
}
};


2) If the data member is a pointer (char *), is std::swap sufficient (even
if the other pointer was allocated with a different size)? For example:

class Test
{
char *s; // assume this is allocated to some size
void swap( Test &rhs )
{
std::swap( s, rhs.s ); // is this okay?
}
};

Thanks,

- Dennis
 
D

Dennis Jones

Dennis Jones said:
Hi all,

1) Let's say you have two char []'s of the same size. How would you write
a no-fail swap method for them? For example:

class Test
{
char s[10];
void swap( Test &rhs )
{
// is it possible to swap the data member 's' with the no-fail
guarantee?
}
};

Well, after thinking about it for a few minutes, I came up with this:

void strswap( char * const c1, char * const c2 )
{
int size = std::max( strlen(c1), strlen(c2) ) + 1;
for ( int c=0; c<size; ++c )
{
std::swap( c1[c], c2[c] );
}
}

class Test
{
char s[10]; // guaranteed to be a valid string
void swap( Test &rhs )
{
strswap( s, rhs.s );
}
};

Is this reasonable?

- Dennis
 
A

Alf P. Steinbach

* Dennis Jones:
Dennis Jones said:
Hi all,

1) Let's say you have two char []'s of the same size. How would you write
a no-fail swap method for them? For example:

class Test
{
char s[10];
void swap( Test &rhs )
{
// is it possible to swap the data member 's' with the no-fail
guarantee?
}
};

Well, after thinking about it for a few minutes, I came up with this:

void strswap( char * const c1, char * const c2 )
{
int size = std::max( strlen(c1), strlen(c2) ) + 1;
for ( int c=0; c<size; ++c )
{
std::swap( c1[c], c2[c] );
}
}

The questions you posed looked like homework.

But since you now have made an effort:

It was not specified that the arrays contained nullterminated strings,
hence std::strlen is contra-indicated. It was specified that the arrays
were of the same size, hence std::max is contra-indicated.


class Test
{
char s[10]; // guaranteed to be a valid string
void swap( Test &rhs )
{
strswap( s, rhs.s );
}
};

Is this reasonable?

No.

For the in-practice of providing swappable strings, use std::string.


Cheers, & hth.,

- Alf
 
D

Dennis Jones

Well, after thinking about it for a few minutes, I came up with this:

void strswap( char * const c1, char * const c2 )
{
int size = std::max( strlen(c1), strlen(c2) ) + 1;
for ( int c=0; c<size; ++c )
{
std::swap( c1[c], c2[c] );
}
}

The questions you posed looked like homework.

Hi Alf, I was hoping you or Victor would respond!

No. I am a professional, though I do work from home, so I guess you *could*
call it homework! But it is NOT schoolwork. The example code I provided is
just to demonstrate what I would like to do in an existing application to
improve the reliability of the existing code and the strswap() function
therein (which is awful):

void strswap( char * const c1, char * const c2 )
{
char *temp = strdup( c1 );
strcpy( c1, c2 );
strcpy( c2, temp );
free( temp );
}

But since you now have made an effort:

It was not specified that the arrays contained nullterminated strings,

I added the "// guaranteed to be a valid string" comment to indicate that
they are null-terminated (I know, I didn't say so in my first post). The
classes all have constructors which guarantee that the members are either
initialized to zero, or otherwise initialized to a valid string.

hence std::strlen is contra-indicated.

I understand your point, but they *will* be null-terminated.

It was specified that the arrays were of the same size, hence std::max is
contra-indicated.

I understand your point here too. The purpose of using strlen and max was
to determine the minimum number of characters I could swap and not lose any
data. There's no point in swapping the entire content of the arrays if they
don't need to be.

Now, if I were to explicitly do the work in the swap member function, then I
*could* do:

class Test
{
char s[10];
void swap( Test &rhs )
{
for ( int i=0; i<sizeof(s)/sizeof(s[0]); ++i )
{
std::swap( s, rhs.s );
}
}
};

That does avoid the extra strlen and max calls (which might affect the
efficiency of the function). However, I would prefer not to have to
re-write the same thing over and over in many different classes that contain
many char[] data members (DRY principle), which is why I wrote the
standalone strswap() function [so I wouldn't have to write the same code
over and over for every char[] data member in every class -- that would be
ridiculous!!].

So, for something that is re-usable, I opted for a standalone function. To
be generic enough to use it with any of the char[] data members in all those
classes, it was necessary to loop based on the data itself. Though, I
suppose I could also do:

void strswap( char * const c1, char * const c2, size_t length )
{
for ( int i=0; i<length; ++i )
{
std::swap( c1, c2 );
}
}

class Test
{
char s[C]; // where C is some constant
void swap( Test &rhs )
{
strswap( s, rhs.s, sizeof(s)/sizeof(s[0]) );
}
};

For the in-practice of providing swappable strings, use std::string.

I do use std::string (actually a variant of it) with newer code, but I don't
have that option here (not without a LOT of re-writing) because this is very
old, legacy code.

All of the examples do the same basic thing, and that is to swap the
contents of two char arrays. My question isn't whether or not strlen or max
is contra-indicated, as if it were a homework assignment. My question is,
how do I provide the no-fail guarantee when swapping an array of 'sometype'
(we might just as well be talking about an array of any type)?

So, to be more specific, is a loop a satisfactory way to swap array elements
and still provide the no-fail guarantee? I can't think of any other way --
can you?

- Dennis
 
B

Barry

Dennis said:
Well, after thinking about it for a few minutes, I came up with this:

void strswap( char * const c1, char * const c2 )
{
int size = std::max( strlen(c1), strlen(c2) ) + 1;
for ( int c=0; c<size; ++c )
{
std::swap( c1[c], c2[c] );
}
}
The questions you posed looked like homework.

Hi Alf, I was hoping you or Victor would respond!

No. I am a professional, though I do work from home, so I guess you *could*
call it homework! But it is NOT schoolwork. The example code I provided is
just to demonstrate what I would like to do in an existing application to
improve the reliability of the existing code and the strswap() function
therein (which is awful):

void strswap( char * const c1, char * const c2 )
{
char *temp = strdup( c1 );
strcpy( c1, c2 );
strcpy( c2, temp );
free( temp );
}

the std::swap in algorithm requires the type to be Assignable,
so with /char*/, you can simply use std::swap.

while array is not Assignable, so you have to do the extra job as you
did above.
But since you now have made an effort:

It was not specified that the arrays contained nullterminated strings,

I added the "// guaranteed to be a valid string" comment to indicate that
they are null-terminated (I know, I didn't say so in my first post). The
classes all have constructors which guarantee that the members are either
initialized to zero, or otherwise initialized to a valid string.

hence std::strlen is contra-indicated.

I understand your point, but they *will* be null-terminated.

It was specified that the arrays were of the same size, hence std::max is
contra-indicated.

I understand your point here too. The purpose of using strlen and max was
to determine the minimum number of characters I could swap and not lose any
data. There's no point in swapping the entire content of the arrays if they
don't need to be.

Now, if I were to explicitly do the work in the swap member function, then I
*could* do:

class Test
{
char s[10];
void swap( Test &rhs )
{
for ( int i=0; i<sizeof(s)/sizeof(s[0]); ++i )
{
std::swap( s, rhs.s );
}


the for loop can be replaced by std::swap_ranges

std::swap_ranges(s, s + sizeof(s)/sizeof(s[0]), rhs.s);
}
};

That does avoid the extra strlen and max calls (which might affect the
efficiency of the function). However, I would prefer not to have to
re-write the same thing over and over in many different classes that contain
many char[] data members (DRY principle), which is why I wrote the
standalone strswap() function [so I wouldn't have to write the same code
over and over for every char[] data member in every class -- that would be
ridiculous!!].

So, for something that is re-usable, I opted for a standalone function. To
be generic enough to use it with any of the char[] data members in all those
classes, it was necessary to loop based on the data itself. Though, I
suppose I could also do:

void strswap( char * const c1, char * const c2, size_t length )
{
for ( int i=0; i<length; ++i )
{
std::swap( c1, c2 );
}
}

class Test
{
char s[C]; // where C is some constant
void swap( Test &rhs )
{
strswap( s, rhs.s, sizeof(s)/sizeof(s[0]) );
}
};

For the in-practice of providing swappable strings, use std::string.

I do use std::string (actually a variant of it) with newer code, but I don't
have that option here (not without a LOT of re-writing) because this is very
old, legacy code.

All of the examples do the same basic thing, and that is to swap the
contents of two char arrays. My question isn't whether or not strlen or max
is contra-indicated, as if it were a homework assignment. My question is,
how do I provide the no-fail guarantee when swapping an array of 'sometype'
(we might just as well be talking about an array of any type)?

So, to be more specific, is a loop a satisfactory way to swap array elements
and still provide the no-fail guarantee? I can't think of any other way --


I guess you meant "no throw guarantee",
the answer is Yes
 
V

Victor Bazarov

Dennis said:
[..]
Now, if I were to explicitly do the work in the swap member function,
then I *could* do:

class Test
{
char s[10];
void swap( Test &rhs )
{
for ( int i=0; i<sizeof(s)/sizeof(s[0]); ++i )
{
std::swap( s, rhs.s );
}
}
};

That does avoid the extra strlen and max calls (which might affect the
efficiency of the function). However, I would prefer not to have to
re-write the same thing over and over in many different classes that
contain many char[] data members (DRY principle), which is why I
wrote the standalone strswap() function [so I wouldn't have to write
the same code over and over for every char[] data member in every
class -- that would be ridiculous!!].
[..]


Make it a template, something like

template<unsigned s> void strswap(char (&a1), char (&a2))
{
for (unsigned i = s; i-- > 0; )
std::swap(a1, a2);
}

and reuse it inside 'swap' function in each class that has a char[]
member.

V
 
D

Dennis Jones

the std::swap in algorithm requires the type to be Assignable,
so with /char*/, you can simply use std::swap.

Thanks, I pretty much figured that one out.

while array is not Assignable, so you have to do the extra job as you did
above.

Thank you.

the for loop can be replaced by std::swap_ranges

std::swap_ranges(s, s + sizeof(s)/sizeof(s[0]), rhs.s);

Good point. There are so many algorithms, it's easy to forget what's
available.

I guess you meant "no throw guarantee",
the answer is Yes

No, I mean "no-fail," as mentioned in Sutter & Alexandrescu's "C++ Coding
Standards", items 51 & 56.

Thanks for the feedback and confirmation.

- Dennis
 
D

Dennis Jones

Make it a template, something like

template<unsigned s> void strswap(char (&a1), char (&a2))
{
for (unsigned i = s; i-- > 0; )
std::swap(a1, a2);
}


You know Victor, I was wondering if there was a nice way to do it with a
template. Okay, so let's say I use your template and do this:

class Test
{
char str[10];
void swap( Test &rhs )
{
strswap( str, rhs.str );
}
};

I am not very familiar with that syntax. You are using 's' as a value,
rather than a type. But since the size of the array is known at
compile-time, I presume then that the compiler is able to interpret the
value of 's' based on the argument(s) passed to it? If so, that's pretty
cool!

- Dennis
 
D

Dennis Jones

Victor Bazarov said:
Make it a template, something like

template<unsigned s> void strswap(char (&a1), char (&a2))
{
for (unsigned i = s; i-- > 0; )
std::swap(a1, a2);
}


Shoot. Unfortunately, my compiler, doesn't like this. It gives the error:

"Could not find a match for 'strswap<s>(char *,char *)'"

Here is the test code I tried:

#include <algorithm>

template<unsigned s>
void strswap(char (&a1), char (&a2))
{
for (unsigned i = s; i-- > 0; )
std::swap(a1, a2);
}

class Test
{
public:
char str[10];
void swap( Test &rhs )
{
strswap( str, rhs.str );
// strswap<10>( str, rhs.str ); // I tried this, thinking maybe I needed
to specify the value of 's'
}
};

int main(int argc, char* argv[])
{
Test t1, t2;
strcpy( t1.str, "hello" );
strcpy( t2.str, "world" );
t1.swap( t2 );
return 0;
}

So, either:

1) The compiler doesn't support this template usage (I tried BCB5, BCB6,
BDS2006 and CRS2007 -- all give the same error).
2) I am not using it correctly.

Since it didn't work in any of my compilers, I'm betting my usage is
incorrect. Can you enlighten me?

- Dennis
 
D

Dennis Jones

Shoot. Unfortunately, my compiler, doesn't like this. It gives the
error:

"Could not find a match for 'strswap<s>(char *,char *)'"

A couple of changes got it to work:

template<unsigned s>
void strswap(char (a1), char (a2)) // removed the '&'s
{
for (unsigned i = s; i-- > 0; )
std::swap(a1, a2);
}

class Test
{
public:
char str[10];
void swap( Test &rhs )
{
strswap<10>( str, rhs.str ); // added the "<10>"
}
};

So, apparently, the compiler *cannot* deduce 's' at compile-time (or I'm
still doing something wrong).

- Dennis
 
A

Alf P. Steinbach

* Dennis Jones:
Victor Bazarov said:
Make it a template, something like

template<unsigned s> void strswap(char (&a1), char (&a2))
{
for (unsigned i = s; i-- > 0; )
std::swap(a1, a2);
}


Shoot. Unfortunately, my compiler, doesn't like this. It gives the error:

"Could not find a match for 'strswap<s>(char *,char *)'"

Here is the test code I tried:

#include <algorithm>

template<unsigned s>
void strswap(char (&a1), char (&a2))
{
for (unsigned i = s; i-- > 0; )
std::swap(a1, a2);
}

class Test
{
public:
char str[10];
void swap( Test &rhs )
{
strswap( str, rhs.str );
// strswap<10>( str, rhs.str ); // I tried this, thinking maybe I needed
to specify the value of 's'
}
};

int main(int argc, char* argv[])
{
Test t1, t2;
strcpy( t1.str, "hello" );
strcpy( t2.str, "world" );
t1.swap( t2 );
return 0;
}

So, either:

1) The compiler doesn't support this template usage (I tried BCB5, BCB6,
BDS2006 and CRS2007 -- all give the same error).


Compiles fine with MSVC7.1, g++ 3.4.4 and Comeau Online 4.3.9.

Especially the last indicates the code is OK at the level of
compiles-or-not.

Borland 5 (if that's BCB5) is pretty old. I don't know the other
compilers you've listed. Googling it seems the last is an IDE.

2) I am not using it correctly.

Since it didn't work in any of my compilers, I'm betting my usage is
incorrect. Can you enlighten me?

Try a modern / non-Borland compiler?


Cheers, & hth.,

- Alf
 
D

Dennis Jones

Compiles fine with MSVC7.1, g++ 3.4.4 and Comeau Online 4.3.9.

Especially the last indicates the code is OK at the level of
compiles-or-not.

Borland 5 (if that's BCB5) is pretty old. I don't know the other
compilers you've listed. Googling it seems the last is an IDE.


They're all IDE's, but they all include command-line versions of the
compiler, linker, make, etc.

Try a modern / non-Borland compiler?


BCB5 (Borland C++Builder version 5) and Borland C++ 5.0x are NOT the same
product. Borland C++ 5.0x was circa 1997. BCB5 came out in 2000. BCB6
came out in 2002, BDS2006 (Borland Developer Studio) in 2006, and CRS2007
(CodeGear RAD Studio) in 2007. So, they are all relatively "modern"
compilers.

Borland *used* to be known for its standards compliance (back in the early
90's). They aren't so much anymore, but they (now CodeGear, which split off
from Borland last year) are working hard (they better be, darn it!) to
improve compliance in their latest compiler (C++Builder 2007). For example,
being able to build as many of the Boost libraries as possible (Spirit, for
instance) is one of the areas they are focusing on. The latest product
includes the Dinkumware library, and I don't think any of their previous
compilers would have worked with Dinkumware -- so they are making progress.

Anyway, for now, I've settled on the following:

void strswap( char *c1, char *c2 )
{
const int size = std::max( strlen(c1), strlen(c2) ) + 1;
std::swap_ranges( c1, c1+size, c2 );
}

This allows me to benefit from a no-fail swap without having to change any
of the dozens of existing classes that already use it. If I could get
Victor's template version to work without having to specify the array size
(e.g. strswap<N>), I would have preferred that, but since I can't (not
without changing every strswap call), I think my current version is
adequate.

I had to remove 'const' from the pointer parameters because there is some
*really* old code that is still compiled by the 1997 version of the compiler
(which uses an old, buggy STL implementation -- HP/RogueWave, I think), and
its version of swap_ranges wouldn't work with 'const' pointers. Oh well.

Thanks Alf, Barry, and Victor for your help,

- Dennis
 
D

Dennis Jones

Dennis Jones said:
I had to remove 'const' from the pointer parameters because there is some
*really* old code that is still compiled by the 1997 version of the
compiler (which uses an old, buggy STL implementation -- HP/RogueWave, I
think), and its version of swap_ranges wouldn't work with 'const'
pointers. Oh well.

I take that back. I can use const_cast<char*> to work around the
HP/RogueWave bug.

- Dennis
 
D

Dennis Jones

Try a modern / non-Borland compiler?

Or, report the bug and ask the vendor to fix it.

As it turns out, the bug (cannot convert from char[] to char(&)[], but
converts to char * instead) only exists for non-static data members.
Non-members and static data members both work fine. Unfortunately, I need
non-static members to work or I can't use the template function in my swap
routine! The vendor (CodeGear) has opened a ticket for the bug and will be
addressing it. Even if that does me no good at the moment, it's a good
sign.

- Dennis
 

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,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top