Please help with testing & improving a StringValue class

  • Thread starter Alf P. Steinbach
  • Start date
A

Alf P. Steinbach

I once suggested in [comp.std.c++] that SomeOne Else(TM) should propose
a string value class that accepted literals and char pointers and so on,
with possible custom deleter, and in case of literal strings just
carrying the original pointer.

In other words, for the simplest usage code:

* no overhead (just carrying a pointer or two), and

* no possibility of exceptions (for that case).

For example, in an exception it's not so good when the string carrier
can throw on construction due to dynamic allocation, and for another
example, passing std::string values around can involve dynamic
allocations and deallocations, and those are very inefficient.

So yesterday evening I fired up an editor and coded a little bit. /Not/
using test-driven development: this code changed significantly as it
clarified! Therefore, there aren't any tests, as yet, and

* it would be nice if some TDD person could device and run proper
tests (it would also be interesting to see them!), and/or

* if other persons could simply try to use this class and report on
bugs, compiler incompatibilities, needed functionality (I don't know
what functionality it should provide in addition to carrying
strings) etc., whatever's relevant.

The code uses boost::intrusive_ptr from the Boost library, which
therefore is required to compile. Strings with embedded zero characters
are not supported in the current code. I don't think the need is great.

Example usage code


StringValue foo()
{
return "No dynamic allocation, no possible exception, fast";
}

StringValue bar()
{
return std::string( "A dynamic" ) + " copy";
}


Example exercising all currently defined constructors, where malloc and
free is used just to demonstrate that also that is possible:

<code>
#include <alfs/StringValueClass.hpp>
#include <iostream>
#include <cstdlib> // std::malloc, std::free
#include <cstring> // std::strcpy, std::strlen

char const* mallocStr( char const s[] )
{
using namespace std;
return strcpy( static_cast<char*>( malloc( strlen( s ) + 1 ) ), s );
}

void myDeleter( void const* p ) { std::free( const_cast<void*>( p ) ); }

int main()
{
// A StringValue can be freely copied and assigned, but the value
// can not be modified.

using namespace alfs;

char const* const dynValue = "dynamic copy";
char const* const ptrValue = "pointer to persistent buffer";
char const* const customValue = "custom delete";
char const sizedValue[] = { 's', 'i', 'z', 'e', 'd' };

StringValue literal( "literal" ); // No alloc.
StringValue pointer( ptrValue, NoDelete() ); // No alloc.
StringValue custom( mallocStr( customValue ), myDeleter );
StringValue sized( sizedValue, sizeof( sizedValue ) );
StringValue dynamic( dynValue );
StringValue stdval( std::string( "std::string" ) );

std::cout << literal << std::endl;
std::cout << pointer << std::endl;
std::cout << custom << std::endl;
std::cout << sized << std::endl;
std::cout << dynamic << std::endl;
std::cout << stdval << std::endl;
}
</code>

Code currently available (especially if you want to help testing and or
discussing functionality or coding, whatever) at
<url: home.no.net/alfps/cpp/lib/alfs_v00.zip> (lawyers, if any: note
that I retain copyright etc., although usage is of course permitted).

Cheers, & hope this can be interesting,

- Alf
 
B

Barry

Gianni said:
Alf P. Steinbach wrote:
...
StringValue literal( "literal" ); // No alloc.

Is it legal to do:

literal[0] = 'L'; ?
int main()
{
// A StringValue can be freely copied and assigned, but the value
// can not be modified.
 
B

Barry

Alf said:
I once suggested in [comp.std.c++] that SomeOne Else(TM) should propose
a string value class that accepted literals and char pointers and so on,
with possible custom deleter, and in case of literal strings just
carrying the original pointer.

In other words, for the simplest usage code:

* no overhead (just carrying a pointer or two), and

* no possibility of exceptions (for that case).

For example, in an exception it's not so good when the string carrier
can throw on construction due to dynamic allocation, and for another
example, passing std::string values around can involve dynamic
allocations and deallocations, and those are very inefficient.

So yesterday evening I fired up an editor and coded a little bit. /Not/
using test-driven development: this code changed significantly as it
clarified! Therefore, there aren't any tests, as yet, and

* it would be nice if some TDD person could device and run proper
tests (it would also be interesting to see them!), and/or

* if other persons could simply try to use this class and report on
bugs, compiler incompatibilities, needed functionality (I don't know
what functionality it should provide in addition to carrying
strings) etc., whatever's relevant.

The code uses boost::intrusive_ptr from the Boost library, which
therefore is required to compile. Strings with embedded zero characters
are not supported in the current code. I don't think the need is great.

Example usage code


StringValue foo()
{
return "No dynamic allocation, no possible exception, fast";
}

StringValue bar()
{
return std::string( "A dynamic" ) + " copy";
}


Example exercising all currently defined constructors, where malloc and
free is used just to demonstrate that also that is possible:

<code>
#include <alfs/StringValueClass.hpp>
#include <iostream>
#include <cstdlib> // std::malloc, std::free
#include <cstring> // std::strcpy, std::strlen

char const* mallocStr( char const s[] )
{
using namespace std;
return strcpy( static_cast<char*>( malloc( strlen( s ) + 1 ) ), s );
}

void myDeleter( void const* p ) { std::free( const_cast<void*>( p ) ); }

int main()
{
// A StringValue can be freely copied and assigned, but the value
// can not be modified.

using namespace alfs;

char const* const dynValue = "dynamic copy";
char const* const ptrValue = "pointer to persistent buffer";
char const* const customValue = "custom delete";
char const sizedValue[] = { 's', 'i', 'z', 'e', 'd' };

StringValue literal( "literal" ); // No alloc.
StringValue pointer( ptrValue, NoDelete() ); // No alloc.
StringValue custom( mallocStr( customValue ), myDeleter );
StringValue sized( sizedValue, sizeof( sizedValue ) );
StringValue dynamic( dynValue );
StringValue stdval( std::string( "std::string" ) );

std::cout << literal << std::endl;
std::cout << pointer << std::endl;
std::cout << custom << std::endl;
std::cout << sized << std::endl;
std::cout << dynamic << std::endl;
std::cout << stdval << std::endl;
}
</code>

Code currently available (especially if you want to help testing and or
discussing functionality or coding, whatever) at
<url: home.no.net/alfps/cpp/lib/alfs_v00.zip> (lawyers, if any: note
that I retain copyright etc., although usage is of course permitted).

Cheers, & hope this can be interesting,

std:: cout << (dynamic == dynValue);

prints 0

as it calls
StringValue::eek:perator char const* ()

so == is to compare pointer other than lexicographical compare

I think it would be a good idea to provide
operator== to make lexicographical compare.
 
A

Alf P. Steinbach

* Barry:
std:: cout << (dynamic == dynValue);

prints 0

as it calls
StringValue::eek:perator char const* ()

so == is to compare pointer other than lexicographical compare

I think it would be a good idea to provide
operator== to make lexicographical compare.

Thanks.

This bears thinking about, whether the default semantics should be as
raw pointer (above, plus, may use other pointer operators!) or as
logical string, in which case e.g. concatenation would also be nice.

I'm aware that taking the "as string" semantics to the limit, it becomes
impractical (e.g. locales, infamous Turkish "I"), and that's been used
as an argument for keeping std::string lobotomized. There's a trade-off
here between practicality and surprise surprise for the unwary novice,
and I'm not sure but I think C++ wasn't designed for novices... ;-)


Cheers, & thank you for your constructive feedback,

- Alf
 
R

Roland Pibinger

I once suggested in [comp.std.c++] that SomeOne Else(TM) should propose
a string value class that accepted literals and char pointers and so on,
with possible custom deleter, and in case of literal strings just
carrying the original pointer.

In other words, for the simplest usage code:

* no overhead (just carrying a pointer or two), and

* no possibility of exceptions (for that case).

IOW, you created an assignable but otherwise immutable string class
that provides an optimization for string literals.
For example, in an exception it's not so good when the string carrier
can throw on construction due to dynamic allocation, and for another
example, passing std::string values around can involve dynamic
allocations and deallocations, and those are very inefficient.

So yesterday evening I fired up an editor and coded a little bit. /Not/
using test-driven development: this code changed significantly as it
clarified! Therefore, there aren't any tests, as yet, and

* it would be nice if some TDD person could device and run proper
tests (it would also be interesting to see them!), and/or

Ahem, well ...
* if other persons could simply try to use this class and report on
bugs, compiler incompatibilities, needed functionality (I don't know
what functionality it should provide in addition to carrying
strings) etc., whatever's relevant.

The code uses boost::intrusive_ptr from the Boost library, which
therefore is required to compile.

If you want your code to be widely used you should get rid of the
Boost dependency (which seems to be no problem in your case).
Strings with embedded zero characters
are not supported in the current code. I don't think the need is great.

Example usage code


StringValue foo()
{
return "No dynamic allocation, no possible exception, fast";
}

StringValue bar()
{
return std::string( "A dynamic" ) + " copy";
}

In general, the string literal optimization is a good idea. The design
of such a class (template) poses the real challenge. For various
reasons it should hold that sizeof StringValue == sizeof void*. You
need to find a way to distinguish a dynamically allocated array from a
string literal without additional information in the object (not even
an additional flag). One of the reasons for the above is the
requirement of thread safety for string assignment and copying.
Unfortunately there seems to be no way to implement a 'lightweight'
thread-safe assignment operator and/or copy constructor because
incrementing/decrementing the reference-counter and assignment of the
pointer are always two distinct operations. I experimented with my own
string class but could not reach a satisfactory result WRT thread
safety (i.e. when the object is accessed by multiple threads).
Example exercising all currently defined constructors, where malloc and
free is used just to demonstrate that also that is possible:

<code>
#include <alfs/StringValueClass.hpp>
#include <iostream>
#include <cstdlib> // std::malloc, std::free
#include <cstring> // std::strcpy, std::strlen

char const* mallocStr( char const s[] )
{
using namespace std;
return strcpy( static_cast<char*>( malloc( strlen( s ) + 1 ) ), s );
}

void myDeleter( void const* p ) { std::free( const_cast<void*>( p ) ); }

int main()
{
// A StringValue can be freely copied and assigned, but the value
// can not be modified.

using namespace alfs;

char const* const dynValue = "dynamic copy";
char const* const ptrValue = "pointer to persistent buffer";
char const* const customValue = "custom delete";
char const sizedValue[] = { 's', 'i', 'z', 'e', 'd' };

StringValue literal( "literal" ); // No alloc.
StringValue pointer( ptrValue, NoDelete() ); // No alloc.
StringValue custom( mallocStr( customValue ), myDeleter );
StringValue sized( sizedValue, sizeof( sizedValue ) );
StringValue dynamic( dynValue );
StringValue stdval( std::string( "std::string" ) );

std::cout << literal << std::endl;
std::cout << pointer << std::endl;
std::cout << custom << std::endl;
std::cout << sized << std::endl;
std::cout << dynamic << std::endl;
std::cout << stdval << std::endl;
}
</code>

Code currently available (especially if you want to help testing and or
discussing functionality or coding, whatever) at
<url: home.no.net/alfps/cpp/lib/alfs_v00.zip> (lawyers, if any: note
that I retain copyright etc., although usage is of course permitted).

A lot of established Open Source licenses like MIT, new BSD
(http://www.opensource.org/licenses/alphabetical) or ISC
(http://en.wikipedia.org/wiki/ISC_license) are available.
 
B

Barry

Alf said:
I once suggested in [comp.std.c++] that SomeOne Else(TM) should propose
a string value class that accepted literals and char pointers and so on,
with possible custom deleter, and in case of literal strings just
carrying the original pointer.

In other words, for the simplest usage code:

* no overhead (just carrying a pointer or two), and

* no possibility of exceptions (for that case).

For example, in an exception it's not so good when the string carrier
can throw on construction due to dynamic allocation, and for another
example, passing std::string values around can involve dynamic
allocations and deallocations, and those are very inefficient.

So yesterday evening I fired up an editor and coded a little bit. /Not/
using test-driven development: this code changed significantly as it
clarified! Therefore, there aren't any tests, as yet, and

* it would be nice if some TDD person could device and run proper
tests (it would also be interesting to see them!), and/or

* if other persons could simply try to use this class and report on
bugs, compiler incompatibilities, needed functionality (I don't know
what functionality it should provide in addition to carrying
strings) etc., whatever's relevant.

The code uses boost::intrusive_ptr from the Boost library, which
therefore is required to compile. Strings with embedded zero characters
are not supported in the current code. I don't think the need is great.

Example usage code


StringValue foo()
{
return "No dynamic allocation, no possible exception, fast";
}

StringValue bar()
{
return std::string( "A dynamic" ) + " copy";
}


Example exercising all currently defined constructors, where malloc and
free is used just to demonstrate that also that is possible:

<code>
#include <alfs/StringValueClass.hpp>
#include <iostream>
#include <cstdlib> // std::malloc, std::free
#include <cstring> // std::strcpy, std::strlen

char const* mallocStr( char const s[] )
{
using namespace std;
return strcpy( static_cast<char*>( malloc( strlen( s ) + 1 ) ), s );
}

void myDeleter( void const* p ) { std::free( const_cast<void*>( p ) ); }

int main()
{
// A StringValue can be freely copied and assigned, but the value
// can not be modified.

using namespace alfs;

char const* const dynValue = "dynamic copy";
char const* const ptrValue = "pointer to persistent buffer";
char const* const customValue = "custom delete";
char const sizedValue[] = { 's', 'i', 'z', 'e', 'd' };

StringValue literal( "literal" ); // No alloc.
StringValue pointer( ptrValue, NoDelete() ); // No alloc.
StringValue custom( mallocStr( customValue ), myDeleter );
StringValue sized( sizedValue, sizeof( sizedValue ) );
StringValue dynamic( dynValue );
StringValue stdval( std::string( "std::string" ) );

std::cout << literal << std::endl;
std::cout << pointer << std::endl;
std::cout << custom << std::endl;
std::cout << sized << std::endl;
std::cout << dynamic << std::endl;
std::cout << stdval << std::endl;
}
</code>

Code currently available (especially if you want to help testing and or
discussing functionality or coding, whatever) at
<url: home.no.net/alfps/cpp/lib/alfs_v00.zip> (lawyers, if any: note
that I retain copyright etc., although usage is of course permitted).

I tried a look at SharedArray,

template <typename T>
void Deleter(T const* p)
{
delete[] p;
}

SharedArray<int> intArr(new int[10], Deleter<int>);

and have a watch on the data:

- intArr {myRcArray={...} myArray=0x00382b58 } alfs::SharedArray<int>
- myRcArray {p_=0x00382848 }

....

myRefCount 1 unsigned long
- myPointer 0x00382b58 int *
-842150451 int
- myArray 0x00382b58 int *
-842150451 int


it seems that myArray is always equal to myPointer
So is it redundant?
 
A

Alf P. Steinbach

* Roland Pibinger:
I once suggested in [comp.std.c++] that SomeOne Else(TM) should propose
a string value class that accepted literals and char pointers and so on,
with possible custom deleter, and in case of literal strings just
carrying the original pointer.

In other words, for the simplest usage code:

* no overhead (just carrying a pointer or two), and

* no possibility of exceptions (for that case).

IOW, you created an assignable but otherwise immutable string class
that provides an optimization for string literals.

And also for the case of passing around a string with a custom delete
operation, e.g. as provided by many API functions such as Windows'
command line parsing.

When using std::string or std::wstring for this, the API function's
string must first be copied, where dynamic allocation is used, and then
freed (using its own delete operation). This is costly. Then, when
that string should be passed to an API function again, the std::string
must sometimes be copied to dynamically allocated memory using the API's
allocator. Which might happen many times for the same string. This is
costly. A string value class with custom deleter, such as StringValue,
solves that problem. No costly dynamic allocations, and no O(n) copy
operations, for the cases where such operations can be dispensed with by
keeping a delete function along with the string value.

Of course the last can also be accomplished using e.g.
boost::shared_ptr. But then different kinds of strings have to be
treated differently, with conversion among them. And it's awkward
anyway, so awkward that I don't think anybody's done exactly that.


[snip]
If you want your code to be widely used you should get rid of the
Boost dependency (which seems to be no problem in your case).

It think most C++ programmers have the Boost library installed.

But since that's a huge library, it would perhaps be an idea to bundle
the one or few Boost files that's actually used?

intrusive_ptr is just header file code, not separate compilation.

In general, the string literal optimization is a good idea. The design
of such a class (template) poses the real challenge. For various
reasons it should hold that sizeof StringValue == sizeof void*. You
need to find a way to distinguish a dynamically allocated array from a
string literal without additional information in the object (not even
an additional flag). One of the reasons for the above is the
requirement of thread safety for string assignment and copying.
Unfortunately there seems to be no way to implement a 'lightweight'
thread-safe assignment operator and/or copy constructor because
incrementing/decrementing the reference-counter and assignment of the
pointer are always two distinct operations. I experimented with my own
string class but could not reach a satisfactory result WRT thread
safety (i.e. when the object is accessed by multiple threads).

Uhm, that's a different problem. Essentially, if I understand you
correctly, the problem is what trade-off can you do so that in the case
of multi-threaded access to the same string, the total cost of safe
copying is less than with a mutex or whatever? And I think the best
answer is to /not/ accept the premise that multi-threaded access to the
same string without some external thread synchronization such as a
mutex, is something one should support: instead, avoid it!

I think it's in the same league as designing a language to support
arbitrary gotos. That would restrict the language severely (e.g., gotos
past object construction renders all construction guarantees void, so to
support arbitrary gotos, no object construction guarantees). And
instead of designing the language with the goal of supporting
unrestricted gotos, the sensible course is IMHO to restrict gotos.

Example exercising all currently defined constructors, where malloc and
free is used just to demonstrate that also that is possible:

<code>
#include <alfs/StringValueClass.hpp>
#include <iostream>
#include <cstdlib> // std::malloc, std::free
#include <cstring> // std::strcpy, std::strlen

char const* mallocStr( char const s[] )
{
using namespace std;
return strcpy( static_cast<char*>( malloc( strlen( s ) + 1 ) ), s );
}

void myDeleter( void const* p ) { std::free( const_cast<void*>( p ) ); }

int main()
{
// A StringValue can be freely copied and assigned, but the value
// can not be modified.

using namespace alfs;

char const* const dynValue = "dynamic copy";
char const* const ptrValue = "pointer to persistent buffer";
char const* const customValue = "custom delete";
char const sizedValue[] = { 's', 'i', 'z', 'e', 'd' };

StringValue literal( "literal" ); // No alloc.
StringValue pointer( ptrValue, NoDelete() ); // No alloc.
StringValue custom( mallocStr( customValue ), myDeleter );
StringValue sized( sizedValue, sizeof( sizedValue ) );
StringValue dynamic( dynValue );
StringValue stdval( std::string( "std::string" ) );

std::cout << literal << std::endl;
std::cout << pointer << std::endl;
std::cout << custom << std::endl;
std::cout << sized << std::endl;
std::cout << dynamic << std::endl;
std::cout << stdval << std::endl;
}
</code>

Code currently available (especially if you want to help testing and or
discussing functionality or coding, whatever) at
<url: home.no.net/alfps/cpp/lib/alfs_v00.zip> (lawyers, if any: note
that I retain copyright etc., although usage is of course permitted).

A lot of established Open Source licenses like MIT, new BSD
(http://www.opensource.org/licenses/alphabetical) or ISC
(http://en.wikipedia.org/wiki/ISC_license) are available.

Thank you.

I think I heard something about the Apache license, too.

Cheers, & thanks for your constructive feedback (much I hadn't thought
about!),

- ALf
 
A

Alf P. Steinbach

* Barry:
I tried a look at SharedArray,

template <typename T>
void Deleter(T const* p)
{
delete[] p;
}

SharedArray<int> intArr(new int[10], Deleter<int>);

and have a watch on the data:

- intArr {myRcArray={...} myArray=0x00382b58 }
alfs::SharedArray<int>
- myRcArray {p_=0x00382848 }

...

myRefCount 1 unsigned long
- myPointer 0x00382b58 int *
-842150451 int
- myArray 0x00382b58 int *
-842150451 int


it seems that myArray is always equal to myPointer
So is it redundant?

'myArray' is logically redundant when there is reference counting
(dynamic allocation), but in that case provides a few nano-seconds
faster access...

'myArray' is needed for the case where there is no reference counting,
i.e. for the case of no dynamic allocation -- and that's also the
reason why boost::intrusive_ptr is used instead of boost::shared_array
(which always uses or may use dynamic allocation).

And that is half of what this class is about: avoiding those pesky
dynamic allocations and O(n) copy operations where they're not needed.
Copy construction and assignment is always constant time with no
inefficient dynamic allocation. I think that's a nice feature. :)

Cheers,

- Alf
 
A

Alf P. Steinbach

* Alf P. Steinbach:

An 01 version of StringValue is now available at
<url: http://home.no.net/alfps/cpp/lib/alfs_v01.zip>.

Seems I've partially rediscovered the joy of coding...

Old features:

* A StringValue can be constructed from a literal in constant
time with no dynamic allocation.

* A StringValue can be constructed from a pointer and deletion
operation (functor or function) in constant time, with no O(n)
copying.

* A StringValue can be copied and assigned in constant time with
no dynamic allocation.

* A StringValue can be safely constructed in other ways, including
from a 'char const*' and from a 'std::string', but then involving
O(n) copying and dynamic allocation.

* A StringValue can be freely copied and assigned, but the value
can not be modified.

New features:

* A license reference (Boost license) is included in every file,
resulting from comments by Roland Pibinger (thanks).

* operator==, operator< added,
resulting from comments by Barry <[email protected]> (thanks).

* Implicit conversion to 'char const*' /removed/, because

* StringValue is now a subclass of a new class StringValueOrNull,
which supports passing null-values around. A StringValue is
therefore implicitly convertible to StringValueOrNull. A
StringValueOrNull value can only be explicitly converted to pure
StringValue, then with checking of nullvalue & possible exception.

* Support for << and >> stream i/o added (because of removal of
implicit conversion to 'char const*').

Planned features:

+ In order to be useful in Windows programming, a wchar_t version is
needed, i.e. templatization on the character type.

Comments, ideas, criticism etc. welcome!

Cheers, & hope this can be interesting,

- Alf
 
A

Alf P. Steinbach

* Gianni Mariani:
Alf P. Steinbach wrote:
...

Is it thread safe?

As much as any general class. ;-)

I don't believe in adding mutual exclusion to basic operations for a
general purpose class, but I understand the question: with non-mutable
values, it's natural to wonder whether that's in support of threading.

As I see it, adding mutual exclusion everywhere is inefficient, drags in
a lot of threading support code not otherwise needed, and leads to
spaghetti design when people feel "safe" doing anything whatsoever in
threads: IMHO threading code needs to be designed with care, not
accessing variables in other threads willy-nilly.

Cheers,

- Alf


PS: A more serious question: is it slicing safe? Version 01 is not, in
the sense that it's possible to use references or pointers so that one
ends up with a StringValue that's null (with value copied from a
StringValueOrNull). But I'm working on that, namely having
StringValueOrNull and StringValue derive from a common abstract base,
instead of having StringValue derive from StringValueOrNull. Now if
only I could make SFINAE & boost::disable_if work properly! :)
 
B

Barry

Alf said:
I once suggested in [comp.std.c++] that SomeOne Else(TM) should propose

Alf,
I made some modification to v00

Aim:
Use policy for `DeleteFunc' template parameter for `SharedArray'
the DeleteFunc policy class should contain
static void Detete(T* p);

then `SharedArray' has no need to store the free function as member

I'll mail you the code that I modify, since the modification is sparse.
 
A

Alf P. Steinbach

* Barry:
Alf said:
I once suggested in [comp.std.c++] that SomeOne Else(TM) should propose

Alf,
I made some modification to v00

Aim:
Use policy for `DeleteFunc' template parameter for `SharedArray'
the DeleteFunc policy class should contain
static void Detete(T* p);

then `SharedArray' has no need to store the free function as member

I'll mail you the code that I modify, since the modification is sparse.

I got your code before I read the above explanation. I struggled a bit
to make it report memory leaks (why the &%¤/(()! doesn't that code
report leaks?), since I didn't notice you'd added a policy. But did
notice the main program changes, which "should" have caused some leaks...

It saves four or eight bytes (32-bit/64-bit machine) per ref-counted
instance, at the (marginal) cost of requiring a free deleter function to
have extern linkage or be wrapped in a class.

Extern linkage: I'm thinking any free extern linkage function can be
automatically mapped to a suitable functor class; no need for client
code to define them, at least not if I'm not muddled in my thinking.


Cheers, & thanks,

- Alf
 
B

Barry

Alf said:
* Barry:
Alf said:
I once suggested in [comp.std.c++] that SomeOne Else(TM) should propose

Alf,
I made some modification to v00

Aim:
Use policy for `DeleteFunc' template parameter for `SharedArray'
the DeleteFunc policy class should contain
static void Detete(T* p);

then `SharedArray' has no need to store the free function as member

I'll mail you the code that I modify, since the modification is sparse.

I got your code before I read the above explanation. I struggled a bit
to make it report memory leaks (why the &%¤/(()! doesn't that code
report leaks?), since I didn't notice you'd added a policy. But did
notice the main program changes, which "should" have caused some leaks...

It saves four or eight bytes (32-bit/64-bit machine) per ref-counted
instance, at the (marginal) cost of requiring a free deleter function to
have extern linkage or be wrapped in a class.

Extern linkage: I'm thinking any free extern linkage function can be
automatically mapped to a suitable functor class; no need for client
code to define them, at least not if I'm not muddled in my thinking.
Well, when I was doing that policy design
I came to a question
* Use static function or operator()

choose the latter one, then I have to make an instance of the functor
before calling, just like the tr1::hash. It looks awful, but it has one
advantage, it's stateful, even more, we can then use tr1::bind to form
any functor.

But in this case, no storing the functor makes the statefulness
impossible, I think a static function is enough, so I made this choice.
 
G

Greg Herlihy

An 01 version of StringValue is now available at
<url: http://home.no.net/alfps/cpp/lib/alfs_v01.zip>.


New features:

* A license reference (Boost license) is included in every file,
resulting from comments by Roland Pibinger (thanks).

* operator==, operator< added,
resulting from comments by Barry <[email protected]> (thanks).

* Implicit conversion to 'char const*' /removed/, because

* StringValue is now a subclass of a new class StringValueOrNull,
which supports passing null-values around. A StringValue is
therefore implicitly convertible to StringValueOrNull. A
StringValueOrNull value can only be explicitly converted to pure
StringValue, then with checking of nullvalue & possible exception.

* Support for << and >> stream i/o added (because of removal of
implicit conversion to 'char const*').

Comments, ideas, criticism etc. welcome!

Overall, the StringValue class looks very well-designed and implemented.

I do have two small criticisms. First, the StringValue and SharedArray
swap() routines should not be declared in the std:: namespace. As the
comment points out, declaring these swap() routines in the std namespace is
a no-no. Besides, the std namespace is not the optimal place to declare a
swap() routine for a user-defined type - because only those routines in the
std namespace are likely to find it.

A better place to declare a user-defined swap() routine (and where the
Standard actually expects to find it) would be in the same namespace as the
type being swapped (in this case, the "alfs" namespace). Declaring the
swap() routine in the alfs namespace would mean that all routines - not just
those in the std namespace - should be able find it (courtesy of
argument-dependent lookup (ADL)).

Second, I don't see how the concept of "null" is compatible with a
StringValue class (with the emphasis on "value"). In other words, a
StringValue object should have a value (even if it's just an empty string).
The last thing that most C++ programmers want from a utility class is a
"special" valued object of that class that has to be treated differently
than all of the others. And if the program needs to distinguish a null
pointer from an empty string, then it should do so before instantiating a
StringValue object.

So my suggestion is that a StringValue object initialized with a null
pointer constant should either throw an exception (probably not warranted)
or treat the null pointer as an empty string (probably the safer, more
reasonable behavior). After all, the Standard Library's own string class,
std::string, has no concept of a "null" string - and I have never known
anyone to complain about its absence.

Greg
 
A

Alf P. Steinbach

* Greg Herlihy:
Overall, the StringValue class looks very well-designed and implemented.

I do have two small criticisms. First, the StringValue and SharedArray
swap() routines should not be declared in the std:: namespace. As the
comment points out, declaring these swap() routines in the std namespace is
a no-no. Besides, the std namespace is not the optimal place to declare a
swap() routine for a user-defined type - because only those routines in the
std namespace are likely to find it.

A better place to declare a user-defined swap() routine (and where the
Standard actually expects to find it) would be in the same namespace as the
type being swapped (in this case, the "alfs" namespace). Declaring the
swap() routine in the alfs namespace would mean that all routines - not just
those in the std namespace - should be able find it (courtesy of
argument-dependent lookup (ADL)).

Thanks, I'll try that!

Or at least, think about it.

My original thinking was that client code that does

std::swap( a, b );

should just work -- efficiently. But that thinking was not deep
enough. Because with StringValue, assignment, which is probably what
that will result in without overloading that function, is constant time
and with no dynamic allocation, i.e. very efficient anyway.

But it's a shame that std::swap doesn't automatically adjust to classes
with swap member function defined (would an implementation that did
that, be conforming?).

And ditto for std::sort.

Second, I don't see how the concept of "null" is compatible with a
StringValue class (with the emphasis on "value"). In other words, a
StringValue object should have a value (even if it's just an empty string).
The last thing that most C++ programmers want from a utility class is a
"special" valued object of that class that has to be treated differently
than all of the others. And if the program needs to distinguish a null
pointer from an empty string, then it should do so before instantiating a
StringValue object.

So my suggestion is that a StringValue object initialized with a null
pointer constant should either throw an exception (probably not warranted)

It does (and did).

or treat the null pointer as an empty string (probably the safer, more
reasonable behavior).

No, that's what the StringValueOrNull class is for: with pure
StringValue I think it's better to catch that as an error or failure (it
also asserts). StringValueOrNull is not meant as a full-featured class.
It's just available as a "compatible" carrier class for the cases
where you need to distinguish null-values, such as from a database lookup.

Or perhaps to cater to the requirements of old C code.

And I finally got the SFINAE / boost::disable_if stuff to work, so now
these two classes, StringValueOrNull and StringValue, are siblings, with
implicit conversion to StringValueOrNull, and only explicit conversion
the other way, then with checking of nullvalue and possible exception
(both conversions constant time no dynamic allocation).

After all, the Standard Library's own string class,
std::string, has no concept of a "null" string - and I have never known
anyone to complain about its absence.

Oh, they do... :)

Except the "solution" is commonly to use C strings and pointers (ugh).

And one funny solution in one project, in Java, encoding the null value
as a special UUID string value (no fuss with transferring over Corba).


Cheers, and thanks,

- Alf
 
A

Alf P. Steinbach

* Barry:
Well, when I was doing that policy design
I came to a question
* Use static function or operator()

choose the latter one, then I have to make an instance of the functor
before calling, just like the tr1::hash. It looks awful, but it has one
advantage, it's stateful, even more, we can then use tr1::bind to form
any functor.

But in this case, no storing the functor makes the statefulness
impossible, I think a static function is enough, so I made this choice.

On reflection, I think what you mention here about statefulness is
important. Using the policy based approach could be a premature
optimization. After all, there probably will not be that many instances
around at a time... :)

Btw., I've posted the latest, which now has wide string support (see
else-thread).

Cheers, & thanks,

- Alf
 
A

Alf P. Steinbach

* Alf P. Steinbach:

Reusing most of the text I posted for the 01 version:

An 02 version of StringValue is now available at
<url: http://home.no.net/alfps/cpp/lib/alfs_v02.zip>.

Yep, seems like I've partially rediscovered the joy of coding...

Old features:

* A StringValue can be constructed from a literal in constant
time with no dynamic allocation.

* A StringValue can be constructed from a pointer and deletion
operation (functor or function) in constant time, with no O(n)
copying.

* A StringValue can be copied and assigned in constant time with
no dynamic allocation (great for e.g. standard containers).

* A StringValue can be safely constructed in other ways, including
from a 'char const*' and from a 'std::string', but then involving
O(n) copying and dynamic allocation.

* A StringValue can be freely copied and assigned, but the value
can not be modified.

* A license reference (Boost license) is included in every file,
resulting from comments by Roland Pibinger (thanks).

* operator==, operator< added,
resulting from comments by Barry <[email protected]> (thanks).

* Implicit conversion to 'char const*' /removed/, because

* A class StringValueOrNull was added, which supports passing
null-values around. A StringValue is implicitly convertible to
StringValueOrNull. A StringValueOrNull value can only be explicitly
converted to pure StringValue, then with checking of nullvalue &
possible exception.

* Support for << and >> stream i/o added (because of removal of
implicit conversion to 'char const*').

New features:

* In order to be useful in Windows programming, wchar_t versions of
StringValue (WStringValue) and StringValueOrNull (ditto) have
been added, i.e. templatization on the character type.

* Free function swap implementations moved from namespace std to
namespace alfs, resulting from comments by Greg Herlihy (thanks).

* Two small example usage programs, one an abstraction of the 'main'
arguments (with almost no overhead), and one ditto showing a simple
abstraction of Windows Unicode command line arguments.

Comments, ideas, criticism etc. welcome! Note: almost not tested code.
At least not formally tested!

<< and >> i/o for wide streams not implemented, because wide streams are
not implemented by MingW g++ 3.4.4.

(I'm wondering whether SharedArray should provide indexing, and/or
perhaps keep track of the length of the array: perplexingly and almost
paradoxically, it hasn't been needed. I'm also wondering whether there
is some better way to steer constructor selection (in StringValue and
StringValueOrNull) the Right Way, currently using boost::disable_if?)

Cheers, & hope this can be interesting,

- Alf
 
G

Greg Herlihy

(I'm wondering whether SharedArray should provide indexing, and/or
perhaps keep track of the length of the array: perplexingly and almost
paradoxically, it hasn't been needed. I'm also wondering whether there
is some better way to steer constructor selection (in StringValue and
StringValueOrNull) the Right Way, currently using boost::disable_if?)

One idea to help prevent StringValue's constructor from being passed a
const char array when a string literal is expected, would be to offer
a "StringLiteral" (or STRING_LITERAL) macro that clients could use to
designate the string literal initializer explicitly. (This suggestion
is based on a similar macro in Apple's CFString class.)

#define StringLiteral(a) StringValue(""a)

The double-quotes will cause a compile-time error - unless the
initializer "a" is a string literal (that is, it has double-quotes
surrounding it):

StringValue f()
{
const char s[] = "some text";

return StringLiteral(s); // Error: expected primary-expression
before '('

return StringLiteral("some text"); // OK
}

and alternately:

StringValue sv( StringLiteral("a string literal"));

Although I am not a big fan of macros, I will admit that they
occassionally have their uses.

Greg
 

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,756
Messages
2,569,533
Members
45,007
Latest member
OrderFitnessKetoCapsules

Latest Threads

Top