Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

lstring - A lightweight wrapper for std::string

4.39/5 (28 votes)
4 Dec 20053 min read 1   497  
Simplify string handling with a wrapper for std::string.

Introduction

Why should one write a wrapper for a standard C++ string?

  • Convenience: better usability through simplified interface.
  • Extensibility: add, remove, and enhance string functionality.

Background

Motivating example

Assume that you are using std::string in your C++ program and you make a slight mistake, e.g. you erroneously think that the following code will insert "hello" at the beginning of str:

using namespace std;

void test1()
{
  string str ("world!");
  string hello ("Hello, ");
  str.insert (str.begin(), hello); // Error!!
}

Instead the compiler (VC 7.1) generates the following output:

myTestFile.cpp
myTestFile.cpp(12) : error C2664: 'std::basic_string<_Elem,_Traits,_Ax>::_Myt
 &std::basic_string<_Elem,_Traits,_Ax>::insert(std::basic_string<_Elem,_Traits
 ,_Ax>::size_type,const std::basic_string<_Elem,_Traits,_Ax>::_Myt &)' : canno
 t convert parameter 2 from 'std::string' to 'std::basic_string<_Elem,_Traits,
 _Ax>::size_type'
        with
        [
            _Elem=char,
            _Traits=std::char_traits<char>,
            _Ax=std::allocator<char>
        ]
        and
        [
            _Elem=char,
            _Traits=std::char_traits<char>,
            _Ax=std::allocator<char>
        ]
        No user-defined-conversion operator available that can perform this 
        conversion, or the operator cannot be called

The explanation is that 'std::string' is a typedef of template< class charT, class traits = char_traits<charT>, class Allocator = allocator<charT> > class basic_string. The traits and the Allocator template parameters have default values. But you cannot 'typedef away' a type in C++. A typedef is just an alias for the original type. You see the full basic_string template several times in the error message. By the way, with g++ 3.4 the same error produces an error message of more than 40 lines (reformatted to approximately 80 characters per line). You must have seen similar template jumble while using the C++ standard library. The length of the error messages multiplies when std::string is used as a template argument in another template, e.g. in std::vector<std::string> or in std::list<std::string>.

In the following, I use 'std::string' as a shortcut for std::basic_string<...> template.

Getting rid of templates

The basic idea is to write a string class (a class, not a template) that uses std::string as implementation 'back-end' to avoid the template mess and to make the string class extensible for the user. For now the interface is same as that of the std::string interface. Enter lstring, the lightweight string class.

The implementation looks similar to the following code:

class lstring {
// ...
  string_type imp; // explained below

public:
  explicit lstring() {}
  lstring (const charT* s) : imp (s) {}
  lstring (const lstring& str) : imp (str.imp) {}

  lstring& operator= (const lstring& str)
                        { imp = str.imp; return *this; }
  lstring& append (const lstring& str)
                        { imp.append (str.imp); return *this; }
  size_type length() const  { return imp.length(); }
  size_type find (const lstring& str, size_type pos = 0) const
                        { return imp.find (str.imp, pos); }
  int compare (const lstring& str) const
                        { return imp.compare (str.imp); }

// and dozens of other member functions ...
};
  • lstring contains no templates in the interface (almost)
  • Wrappers are provided for iterator and const_iterator; no namespace is used.
  • The implementation mostly forwards calls to the underlying std::string implementation 'back-end'.

I haven't explained what the string_type in the above code snippet means. string_type is the type of the underlying std::string. On Windows platform, string_type automatically adapts to Unicode (via _TCHAR) if _UNICODE is #defined.

class lstring
{
#if defined (_WIN32)
  typedef _TCHAR charT;
#else
  typedef char charT;
#endif
  typedef std::basic_string<charT> string_type;
// ...
};

Much ado about nothing?

What have we gained by wrapping std::sting into a lightweight sting class? Let's first repeat the above example with lstring instead of std::string:

void test2()
{
  lstring str ("world!");
  lstring hello ("Hello, ");
  str.insert (str.begin(), hello); // Error!!
}

The output:

myTestFile.cpp
myTestFile.cpp(12) : error C2664: 'lstring &lstring::insert(lstring::size_typ
e,const lstring &)' : cannot convert parameter 2 from 'lstring' to 'lstring::
size_type'
        No user-defined-conversion operator available that can perform this 
        conversion, or the operator cannot be called

This is still not the error message one expects but, because no templates are involved (i.e. no code generation from templates is involved), it's a lot better than the original std::string error message given above.

Customizing lstring

A second advantage of lstring is that it is not a std::string any more. This means that you can freely change the code and adapt it to your needs.

  • Extending lstring: Although lstring (like the Standard interface) contains a plethora of functions (e.g. 9 replace functions) there are always a few that you want to add, e.g. functions that convert an int to a lstring and vice versa, trim, pad and tokenize functions, or just constructors that take two, three, or more lstring arguments for fast string concatenation. Currently, the only extensions to the standard interface are the three swap() functions that efficiently let you exchange the contents of lstring with std::string.
  • Changing lstring: Do you prefer 'Redmond Style' or 'CamelCase' to STL style for class and function names? After a few 'search/replace' your code might look like this:
    LString myStr (_T("Hello"));
    ULONG pos = myStr.FindFirstOf (_T("lx"));
  • Removing functionality: Some people, for example, argue that a string class should contain minimum number of member functions and use STL-based algorithms instead ([1^], [2^], [3^]).

Using the code

The usage of lstring is not much different from std string:

#include <iostream>
#include "lstring.h"

#if defined (_UNICODE)
#  define std_cout  std::wcout
#else
#  define std_cout  std::cout
#endif

using namespace std;

int main()
{
   lstring hello (_T("Hello, world!"));
   std_cout << hello << endl;
}

Links

Many interesting string articles and implementations can be found in the CodeProject MFC/C++ String section.

Conclusion

The main purpose of lstring is to make string handling easier with standard C++-like string class, implemented as a wrapper for std::string (also known as std::basic_string). lstring also serves as a basis for user defined enhancements and adaptations. A set of unit tests is also included. In the forthcoming article, I will describe the customization of a standard container.

History

  • December 4th, 2005 - First release in CodeProject.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here