Self-managed hashed strings

Posted by David Haley on Sun 13 Feb 2005 02:39 AM — 52 posts, 168,971 views.

USA #0
I've seen a lot of trouble caused by hash strings in SMAUG. The main issue is that there is no type-safety between hashed strings and non-hashed strings, so if you mismatch create/dispose/stralloc/strfree, consequences can be disastrous.

A while ago, I wrote some C++ classes to fix this issue. Basically, it's just a small library to implement managed hashed strings. You assign a value to the string, and it automatically takes care of entering it into the hash table, or only incrementing reference count if it's already present.

While it's not quite presentable for public use at the moment, it would be very easy to make it so. Is anybody interested in this? It's in C++ so it wouldn't be useful to most SMAUG coders unless they feel like moving to C++ - a good thing to do even if you don't use this code - but I know there are at least a few people who do program in C++. Let me know if you're interested and I'll put in a nice little package. :)
Canada #1
I'm fairly certain that I have a clean use of STRALLOC/str_dup, however I would love to have a copy to install. I EVENTUALLY plan to release my code, and if new coders are using it, it would be a great thing to have. Even some safety if I'm not payain attention, god knows thats a regular occurance :)
USA #2
I'd love to use it, I plan on converting to C++ before we go beta. As you can tell, I've had some problems. ;)
USA #3
Alrighty. :) I'll package it up and post it in a day or two.
USA #4
Just to add to these posts, I'd like to see it as well. While I don't plan to move my MUD over to C++, I do other things in C++. This could definitely come in handy :)
USA #5
I too would be interested in seeing this as I am also in the process of converting to C++ and would love nothing more than to say goodbye to STRALLOC/str_dup :)
USA #6
OK, I'm almost done reworking all of this. I wrote the code about two years ago and I've learned an awful lot since. I've reorganized a lot of this code, namely to separate the conceptual notion of a shared string from a hash-table implementation - the hash table is now just a subclass of the shared string manager. This way, you can use whatever implementation of the string manager you want, if you don't like the hash table for some reason. In principle, you could even use a linked-list implementation, but that'd be kind of silly...

I've also used a neat little trick with templates, so that you can create shared strings that use different managers but without having to set a string's manager - it sets itself automatically based on its type.

Basically, you do something like this:
StringManager * gSharedStrManager;

typedef SharedString<&gSharedStrManager> shared_str;

int main()
{
    gSharedStrManager = new HashTable;

    shared_str s = "hello";
    shared_str s2 = "there";
    shared_str s3 = "hello";

    // at this point, there are only two entries in the string manager

    return 0;
}


Of course, if you ever allocate a shared_str without having created its manager, you'll be up a creek without a paddle. :-)

I plan on having a preview (read: undoc'ed) version in a day or two and a documented version a day or so after that.
USA #7
I haven't forgotten about this - I've just been busy with midterms + deadline at work. I'll have it up shortly...
USA #8
No need to rush, I don't need it anytime soon. ;)
USA #9
Here is that preview version I was talking about. I only have Visual Studio build files at the moment but it should be pretty easy to stick it into a Unix project.

http://david.the-haleys.org/tmp/shared-str-v0_9.zip

I will be uploading a more complete version with Unix makefiles, documentation etc. shortly. It'll also include a more complete testing package. In the mean time, comments, criticism or suggestions would be most appreciated. :)
USA #10
I am changing the license to a slightly modified BSD license. I will update the license in the 1.0 release, which will be when I finish the documentation.
#11
Umm...

From sharedstr_manager.h


size_t refCount_; //!< How many times this string is shared
//Among other similar instances


This will break under a conforming compiler. size_t is in the namespace std under C++. You are not allowed to use it without the appropiate scoping.


virtual void dumpTable(std::ostringstream & os) const = 0; // for debugging: dump whole table to os.


Why? Why not just simply use a std::ostream? It still allows a std::ostringstream to be passed to it.

From sharedstr_hastable.h:


protected:
//...
inline size_t hash(const std::string & str) const;


Which will break if a subclass ever tries to use this function. Do not declare a function inline unless you intend to have the code readily available for all files.

And then I turn to your class in shardstr.hpp.

Essentially you have provided a very strange interface for the programmer. You force the programmer to retain the manager variables that he or she creates. Personally, I do not find this very appealing. A better solution would be to have a more class-based solution where each class has a internal static storage so your shared strings can instantate many instances of the class but all the instances would still reference the same shared strings. This is very similar to the STL allocator design.
USA #12
Thank you for your comments, Raz.

Quote:
This will break under a conforming compiler. size_t is in the namespace std under C++. You are not allowed to use it without the appropiate scoping.
size_t is not in the std namespace under c++! It is defined in the global namespace in std.io which is included via including the string header file.
Quote:
Why? Why not just simply use a std::ostream?
Because I wasn't thinking when I wrote that. :-)
Quote:
Which will break if a subclass ever tries to use this function. Do not declare a function inline unless you intend to have the code readily available for all files.
Umm... what?

For starters, there is no reason for a subclass to reimplement the hash function.

Secondly, what do you mean, it will break it?
Quote:
Essentially you have provided a very strange interface for the programmer. You force the programmer to retain the manager variables that he or she creates. Personally, I do not find this very appealing.
What if you want to keep track of the manager publicly to access its statistics? The whole point of this template argument was precisely to keep track of the manager variables - which, incidentally, you only have to store once in a typedef.
Quote:
A better solution would be to have a more class-based solution where each class has a internal static storage so your shared strings can instantate many instances of the class but all the instances would still reference the same shared strings. This is very similar to the STL allocator design.
What if you wanted to have different kinds of shared strings (using e.g. different hash functions), depending on the specific kind of strings you are sharing?
#13
Quote:
size_t is not in the std namespace under c++!


Wrong. size_t is included within the std namespace.

Quote:
It is defined in the global namespace in std.io which is included via including the string header file.


Well, no. It is not defined in cstdio. It is defined in other files, such as cstddef or cstring. Those files may be included by cstdio, but it is not portable.

Anyhow, I'm wrong for other reasons. It seems that C++ kept that the borrowed C types would be available in the global namespace as well as the std namespace. Something I didn't realize.

Quote:
For starters, there is no reason for a subclass to reimplement the hash function.


I never made such a claim.

Quote:
Secondly, what do you mean, it will break it?


Since you declared the hash function inline without providing the definition in a header file (or other suitable file), it would be impossible for potential subclasses to use the hash function. However, this is only a problem if you intended on that class to be subclasses (which I think I thought it was).

Quote:
What if you want to keep track of the manager publicly to access its statistics? The whole point of this template argument was precisely to keep track of the manager variables - which, incidentally, you only have to store once in a typedef.


Your design isn't the only solution to that. You could easily do that with my allocator-like design. The internal static class could keep statisitics which the allocator can access when the programmer needs them.

Quote:
What if you wanted to have different kinds of shared strings (using e.g. different hash functions), depending on the specific kind of strings you are sharing?


That's the beauty of subclassing: you're not limited to the number of children you make. Each hash function could easily have its own subclass. You could even make the design so generic that minimal typing would be necessary for each subclass.
USA #14
Quote:
Wrong. size_t is included within the std namespace.
You were saying that it's in the std namespace and that a 'standards-compliant' compiler would fail. Well, it won't- it's in the global namespace.
Quote:
It is not defined in cstdio
It is for the VS header files. For the g++ header files, it is defined via cstddef which includes stddef.h.
Quote:
The internal static class could keep statisitics which the allocator can access when the programmer needs them.
It seems that you suggest that instead of dragging around a manager, you drag around an allocator. I'm not sure what the gain is, since you felt it 'clumsy' to drag around a manager - which incidentally I disagree with.
Quote:
That's the beauty of subclassing: you're not limited to the number of children you make. Each hash function could easily have its own subclass. You could even make the design so generic that minimal typing would be necessary for each subclass.
You're just shifting the problem. Instead of storing the hash function etc. in a manager, you're making the programmer subclass the shared string class and then you use an allocator to deal with it instead. Personally, I would find it a bother to have to subclass off of the shared string all the time, which is one reason why I didn't do it that way. IMHO it is much cleaner to have a single, generic shared string type without need for subclasses where you can plug in a single, generic type of manager, and to use different hash functions all you need to do is call the set-hash-function method on the manager.

In any case it seems that this is a matter of personal preference. I'd be curious to hear arguments in the absolute about one being 'better' than the other. I don't think you're silly for wanting to do it that way, I just don't like it. I take it that you don't like my approach either, so I'd like to hear if you feel it's a matter of preference or if you have some kind of argument what one is simply better than the other in the absolute.
USA #15
Well, darn... g++ (at least, version 3.4.1) does not accept non-constant values as template arguments. That means that the design as it is will not work with g++, which is not acceptable. I'm going to move to something of a static solution like Raz suggested (although probably not exactly what he has in mind), which was the original plan anyhow - a shame, though, because I preferred this way of doing things.

Funny how VS and G++ differ so completely on this issue. VS accepted it without a peep. I wonder what the standard says about this...
USA #16
Stroustrup says "A template argument can be a constant expression, the address of an object or function with external linkage, or a non-overloaded pointer to member. A pointer used as a template argument must be of the form &of where of is the name of an object or a function, or of the form f, where f is the name of a function. A pointer to member must be of the form &X::of, where of is the name of a member. In particular, a string literal is not acceptable as a template argument."
Amended on Fri 04 Mar 2005 05:12 AM by Flannel
USA #17
I was pretty sure g++'s behavior seemed abnormal, and the standard confirms that. Thanks for pasting that, Flannel. At least I feel better about my understanding of the standard. :-)
USA #18
Well, that just blows. So the string hasher won't work in g++ eh? Hopefully a solution won't involve too much hassle?
USA #19
Fortunately I have a work-around that I just finished implementing - like I said it was the original plan, but I find it less elegant and a little of a hassle. As far as the programmer is concerned, you have to do this:
SharedString::HashTable * gTable;

MAKE_MANAGER_WRAPPER(HashTableWrapper, &gTable)
typedef SharedString::SharedString< HashTableWrapper > shared_str;
The macro there expands to this:
#define MAKE_MANAGER_WRAPPER(name, managerPtr) \
	struct name \
	{ \
		static const std::string * AddString(const std::string & str) \
		{ return (*Manager_)->addString(str); } \
		static void DeleteString(const std::string & str) \
		{ (*Manager_)->deleteString(str); } \
		static SharedString::StringManager ** Manager_; \
	}; \
	SharedString::StringManager ** name::Manager_ = (SharedString::StringManager**) managerPtr;
So, instead of having an address as the template argument, you give it a static class, and when the shared string needs to access the manager it just calls the functions of the static class that (as the name implies) wrap around the manager.

I still do not feel terribly inclined to use an allocator scheme, for a variety of reasons. One thing is that it adds a fair amount of complexity to the scheme and users need (or, at least, should) to understand allocators somewhat to use the system. Until I'm convinced that it's better in the absolute, I'm not sure it's worth the trouble to use it, both from my perspective and that of an API user.

In any case, the code now compiles without warning on g++ 3.4.1 (much stricter than 3.3.3) and on MS VS.net. I'm still surprised that for a change, MS seems to have gotten the standard better than g++. :-)
USA #20
So am I missing something or is my reading of the examples you had put out with the first release correct? All someone needs to do to use this and have them behave more or less like the old style hashstr.c stuff did is to declare something as being a shared_str type variable?

Or are people going to have to jump through a bunch of silly hoops each time they want to use this?
USA #21
All you have to do is make one typedef to e.g. 'shared_str' (or whatever you want), to associate the type with a manager (or you could specify it every time, but why bother?) and then everytime you define a string as a shared_str, it will be automatically managed. Absolutely nothing to worry about - when you assign a string, it'll look it up in the table and add it if necessary; when you destroy (or change) the string it'll decrement the reference count and only delete the shared memory if nobody else is using it.

The main idea was to make something completely hassle-free and I don't think you can get much better than a single typedef. :-)
#22
Quote:
You were saying that it's in the std namespace and that a 'standards-compliant' compiler would fail. Well, it won't- it's in the global namespace.


I did say it would fail, but I thought wrong.

However, it is in the std namespace. It is, at the same time, exposed to the global namespace as well.

Quote:
It is for the VS header files. For the g++ header files, it is defined via cstddef which includes stddef.h.


VS does not define C++. The standard clearly says that size_t is only found in cstddef, cstring, and... cstdlib I think (I'd have to dig through the standard. VS defining size_t in cstdio does not agree with the standard.

Quote:
It seems that you suggest that instead of dragging around a manager, you drag around an allocator. I'm not sure what the gain is, since you felt it 'clumsy' to drag around a manager - which incidentally I disagree with.


Why would you drag around an allocator? It was a goal of mine to not have to do such a thing. For example:


class StringAllocator {
private:
 static InternalAllocator {
   private:
      hash_type hash;
      size_t bytes_allocated;
      //...
   public:
      InternalAllocator();
      const char *Share( const char *sz );
      void Decrease( const char *sz );
      size_t GetBytes( void );
      //...
  };
public:
  StringAllocator();
  //... functions using the internal allocator.
};

template< typename T >
class SharedString {
private:
   T allocator;
   //...
public:
  //operators
};

//There would be no bulk in carrying around an allocator
//Each SharedString class would have its own instance
//of the StringAllocator class, but each instance would
//always reference the internal static allocator
//so you would have a real string sharing mechanism.

//If the user wanted to use the allocator directly,
//he or she could:
void func( void )
{
  StringAllocator sa;
  std::cout << sa.GetBytes() << std::endl;
}


There would be no bulk in carrying around an allocator. There would be minor space added on to each SharedString class, but that would only be a serious problem for MUDs if you dynamically allocated many SharedString instances (which would defeat the purpose).

Quote:
You're just shifting the problem. Instead of storing the hash function etc. in a manager, you're making the programmer subclass the shared string class and then you use an allocator to deal with it instead. Personally, I would find it a bother to have to subclass off of the shared string all the time, which is one reason why I didn't do it that way.


I think I have made myself unclear. You wouldn't subclass the shared string class; rather, you would make subclasses (or even unrealted subclasses) of the allocator. Looking at my rough design above, you could see that you could provide a list of requirements for the template parameter so you could create many allocators that would work in the shared string class.
#23
Quote:
Funny how VS and G++ differ so completely on this issue. VS accepted it without a peep. I wonder what the standard says about this...


Well, the standard says this about it:

Quote:
the address of an object or function with external linkage, including function templates and function
template-ids but excluding non-static class members, expressed as & id-expression where the & is
optional if the name refers to a function or array, or if the corresponding template-parameter is a reference;


It seems that you cannot use your allocator in your design unless it has external linkage.
USA #24
Quote:

The main idea was to make something completely hassle-free and I don't think you can get much better than a single typedef. :-)


Ok, cool deal then. Messy looking macros or otherwise, as long as it's not necessary to mess with the inner workings to use the code I'm happy :)

Looking forward to the finalized version of this.
USA #25
Quote:
VS does not define C++. The standard clearly says that size_t is only found in cstddef, cstring, and... cstdlib I think (I'd have to dig through the standard. VS defining size_t in cstdio does not agree with the standard.
Look, Raz, check the headers yourself.
cstddef
#include <stddef.h>

namespace std
{ 
  using ::ptrdiff_t;
  using ::size_t;
}

cstring includes cstddef
cstdlib includes cstddef
Clearly ::size_t is not in the std namespace! Maybe the standard says otherwise but the two major compilers have it in the global namespace first and import it into the std namespace later.
Quote:
I think I have made myself unclear. You wouldn't subclass the shared string class; rather, you would make subclasses (or even unrealted subclasses) of the allocator. Looking at my rough design above, you could see that you could provide a list of requirements for the template parameter so you could create many allocators that would work in the shared string class.
OK, I see what you want to do now, but I'm still not sure what it gains you to have an allocator as opposed to a manager. My new design - the one that I will release shortly and from which the example above is derived - uses a template argument that specifies a static class that 'wraps around' the manager. I'm just not sure why you think the allocator design is 'better'.

Samson:
Quote:
Messy looking macros or otherwise,
Oh, come on, it's just one macro and you only ever use it once. :-) (Well, ok, two macros that you use each once; once to define the wrapper and once to initialize its manager)
USA #26
So has there been any progress on the updated code? :)
USA #27
Just looking to see if any further progress has been made on this?
USA #28
At long last I have a near-final version of the library up.

http://david.the-haleys.org/index.php?page=shared-str

My apologies to everybody for the delay. The beginning of the quarter was much busier than I'd expected, and by the time the dust settled I'd actually sort of forgotten about this. *sheepish* Anyhow, here's the latest version - enjoy, and as always, please send feedback. And also expect me to reappear in the forums tomorrow... :)
#29
Is anyone else having problems extracting the archive? It might just be my Cygwin...
USA #30
David@dhaley ~/dev/lib/shared-string/dist/tmp
$ ls
LICENSE*  README*  build_unix/  build_win32/  doc/  include/  libtest/  make_docs.sh*  shared-str-0_9b.tgz  src/

David@dhaley ~/dev/lib/shared-string/dist/tmp
$  


I got it extracting under Cygwin. I also extracted it under Windows. What kind of problems are you having? If you downloaded it under Windows, sometimes it screws up the extension by throwing on a tar at the end when you save the file. The file should be shared-str-0_9b.tgz.
USA #31
Ah. My download counter script had a bug in it. Turns out that I had whitespace after the final "?>" in the PHP script, so it was sending that one byte of space with the file. So, the first 31,079 bytes were correct, but there was an extra byte at the end.

I fixed the script and now you should be able to open it without problems.

Sorry for the mishap...
#32
Got it downloaded. Looks go so far, but here is what I see:

In sharedstr.hpp:

inline size_t length() const
		{
			return str_->length();
		}


As a stylistic note, it would be best to typedef the std::string::size_type within your class so you can replace this return type of size_t with std::string's own size_type.

SharedString & operator= (const std::string & rhs)
		{
			// clear the old string
			deleteString();
			// set the new string
			assignString( rhs );

			return *this;
		}

		SharedString & operator= (const SharedString & rhs)
		{
			// clear the old string
			deleteString();
			// set the new string
			assignString( rhs.str_const() );

			return *this;
		}

		SharedString & operator= (const char * rhs)
		{
			deleteString();
			assignString( rhs );

			return *this;
		}


You should add a check to see if the passed parameter is the same string as the class is holding. Let us take this example:


SharedString<Manager> str("Hello");
str = str;


Your current code will break at the assignment operator. The assignment operation would first delete the stored string and then try to re-assign the deleted string.

Otherwise, I see nothing else wrong. I'll look over it a bit more in-depth later.
USA #33
You're right, the typedef is better. I'll throw that into my next version.

And thanks for spotting that assignment bug - could have made for nasty problems if somebody tried to do that. :)

I'll package up the changes tonight and upload version 0.9c with the fixes.
USA #34
Version 0.9.c has been released, with the two fixes suggested by Raz.

Home page:
http://david.the-haleys.org/index.php?page=shared-str

Release notes:
http://david.the-haleys.org/dev/shared-string/doc/files/version_history-txt.html

Download link:
http://david.the-haleys.org/downloads/shared-string-0.9.c.tgz


NOTE: There seems to be a slight problem compiling under some distributions of *nix that do not define stricmp. If you have this problem, you can either implement stricmp or just change it to strcmp (but, in that case, insensitive compare will stop working.) If it's a problem for people, I'll figure out a more permanent solution.
USA #35
I've seen that a number of people have downloaded this, but is anybody using it? Just curious. And if there's any feedback, please be welcome to provide it.

And as a note of random interest, if you google for "shared string library" (with quotes), the library comes up sixth. Whee. :-)
USA #36
I too am having troubles extracting. I get one giant file with everything in it, which I don't think is the intention. Granted whenever I downloaded it gives me a .gz file instead of the .tgz. ::shrug::

If I can ever get it to work I plan to use it for my mud.
USA #37
Are you using the link:
http://david.the-haleys.org/downloads/shared-string-0.9.c.tgz

I just tried it, and I had no problems at all.
USA #38
Most curious. It seems that downloading with IE produces rather dubious results. I downloaded with no problems using Firefox.

Yet another reason to use Firefox! :P

--
On another note, I compiled both under unix and win32 with absolutely zero problems. That is something of a rarity these days. Good work :)
Amended on Thu 16 Feb 2006 03:47 AM by Nick Cash
USA #39
Yeah, I've had funky problems downloading with IE as well. That's why I haven't used IE for anything other than Windows Update for over two years. :-)

I'm glad your compile worked fine. I discovered that I use the function stricmp which seems to be non-standard, and it didn't compile out-of-the-box on one of the systems I installed it on. Fortunately, stricmp is very easy to reimplement.
USA #40
For those instances when you need IE to access a page (it requires ActiveX like Windows Update, for instance) you can use a browser called Maxthon.

It offers the IE rendering engine, ActiveX, tabbed browsing, and most of the security features of Firefox. It also allows plugins/extensions/themes like Firefox.

For pages where you don't want ActiveX, you can block it. It some ways, it is actually BETTER than Firefox.
USA #41
I've released 0.9d of the library. The only new functionality it adds over 0.9c is a new, smarter hash function for the hash table manager. It also adds some documentation.

http://david.the-haleys.org/index.php?page=shared-str

Download:
http://david.the-haleys.org/downloads/shared-string-0.9.d.tgz

Has anybody tried integrating this into a MUD yet? It worked well for me and I'm curious to hear what other people have found.
USA #42
As I've been integrating this into my MUD, I've found a number of things were missing. As a result I added the += and + operators.

Download version 0.9e:
http://david.the-haleys.org/downloads/shared-string-0.9.e.tgz

I doubt my implementations are the most efficient -- I just needed something to work for me to finish sticking this into my MUD.
USA #43
Oh, I also provide an implementation of stricmp if you don't have one already. Make sure you check the documentation for how to use it.
USA #44
Now, just to refresh my memory, this code of yours handles the same kind of string counting as the hashstr.c stuff in Smaug, right? And it's also a substitute for having to use std::string in C++ STL?
USA #45
Yes, it's a shared-string manager. It makes sure that you only have one instance of every string in memory. And it's completely automatic -- no need to worry about STRALLOC and STRFREE vs. DISPOSE, str_dup, etc.

It's not quite a complete substitute for std::string -- it has a few common methods that I've needed but hardly all. That being said, you can call shared_str.str() to get a const std::string& for whatever you'd need to do.

The documentation, while not stellar, isn't too bad -- hopefully it should help explain what's going on and how to use it.

I'm almost done with a near-complete switchover in my code base from old to new string management. I'll have some memory statistics shortly. I'm not expecting much of a memory gain, but you never know. And I have no way of easily measuring CPU use, so I won't bother with that.
Amended on Sun 05 Mar 2006 02:09 AM by David Haley
USA #46
Quote:
Has anybody tried integrating this into a MUD yet?


I do plan to use it in my base. It is just hard to find time to play with it much while school is winding down to spring break.
USA #47
Your library is now fully integrated with my mud. :)
USA #48
Great! Did you have any problems? How is it going for you?

I finished mine as well. It was a horribly tedious process removing the STRALLOCs etc. but I feel much safer now that the majority of them are gone. (I did keep a few just because I was tired of changing everything.) I think it's using about another megabyte of memory, but I'm not entirely sure and it's hard to take exact measurements. I imagine this is due to the overhead of the hash table, but it's a little more than I expected. I'll have to check out the implementation of the buckets and see if I can't come up with something more efficient. (My first implementation was just something "quick and dirty" to make sure that it worked.)

Most important to me would be feedback on the interface of the shared string. Do you find yourself wanting more methods to the underlying string? (e.g. substr, etc.) I found that generally it worked out quite well for me, once I added the +, += and < operators. (I needed < to store the shared_strs in a map.)

Also, is the documentation good? Any places where it wasn't clear, and/or could be better?
USA #49
My base is built from Scratch and doesnt really employ the library to its full extent too well, though that will soon change. Thus, I don't really have any problems just yet. I'll be sure to report back once the base perks up and objects/mobs start making more of an appearance.

Installation was nice and easy, especially since you provided an example program. The only problem I had was due to my own stupidity (I had forgotten I had an older version of the include files laying around...).

For the things I was doing, though rather brief, the + and += methods, along with str() and c_str(), handled everything I needed. A few more methods that the std::string class employs might be useful (substr, etc.) as well, though I doubt it would really be needed.

The documentation was very helpful. I wasn't quite sure what I could and could not do, and it until I read the documentation. It, along with the example program, made it very clear.

Overall it seemed very clear, and everything worked very easily. Definitely one of the best libraries I've delt with in recent times.
USA #50
I'm still planning to make use of this code, I just haven't had the time to do anything with it yet. It looks like it should do exactly what I want though.

I can forsee that adding substr, find, and replace methods would be a huge help if that's at all possible. I have several spots in the code where that would be useful since I'm already using std::string for those now.
USA #51
Version 0.9.f is released. It includes implementation of the < operator, in addition to some more documentation.

I removed the distinction between str and str_const, in favor of only using the constant version. There isn't really any reason to have a method that returns a copy; if you need a copy you can grab the reference and copy that.

On the web page I note some future plans:
http://david.the-haleys.org/index.php?page=shared-str