Mobile String in BREW Revisited-a BrewString Generalization

String in BREW Revisited-a BrewString Generalization


This article details a String implementation that freely uses both char and AECHAR data types. This is actually a generalization of BrewString that allows strings, wide strings, char and AECHAR arrays to be mixed together, such as:

WString w("br");w += "ew";const AECHAR* ae = w.toCharArray();String d ( w);d += ae;

AECHAR or Char?

AECHAR is the wide string data type in BREW, defined as:

typedef uint16 AECHAR;

and as written in the Brew Guidelines:

“BREW is Unicode (wide string) compliant only (no ISOLATIN1/ANSI) except for file names, which are ISOLATIN1/ANSI. Always use AECHAR instead of “char”. Use string services provided in AEEStdLib.h for string manipulation.”

This has two implications:

  1. AECHAR is used everywhere in the API.
  2. Don’t try to manipulate wide strings directly (using wchar and wchar libraries, for example)—there might be differences in type definition.

On the other hand, char is a basic type, widely used and many libraries are based on it or use it extensively. This means that the answer to the above question has to be: “AECHAR and char.”

A Flexible Implementation

A simple solution might be to take a char-based string implementation and specialize it for AECHAR. This is fast but not very rewarding—all the AECHAR/char/wide string/string conversions have to be done manually by the end user. It would be nice to hide all the complexities between a unique façade and give the end user the ability to freely mix strings/wide strings/char and AECHAR arrays.

This was actually one of the goals of BrewString1 but on a different scale—char only. Enlarging the scope of BrewString, by supporting AECHAR directly, looks like a natural and useful addition.

The actual process of transforming BrewString is rather delicate; the automatic conversions from one type to the other make things complicated. A simple

typedef BrewString<AECHAR> WString;

is not enough. What’s needed is:

  1. a type-driven policy to allow different string manipulations for different types.
  2. a non-intrusive way to automatically detect and transform data types, seamlessly integrated with BrewString and not using static data.

Point 1 was implemented in

template <typename T  >struct BrewStringPolicy;

and its AECHAR specialization:

template <>struct BrewStringPolicy <AECHAR>;

Class function members are mainly wrappers around AEEStdLib.h functionality used internally by BrewString; for example:

static int T_STRLEN(const T* s){   return STRLEN(s);}static int T_STRLEN(const AECHAR* s){   return WSTRLEN(s);}

There are cases when, due to a nonstandard implementation, the wrappers become adapters:

static T* T_STRLOWER( T* s){   return STRLOWER(s);}static AECHAR* T_STRLOWER( AECHAR* s){   WSTRLOWER(s);   return s;}

In one case, STRSTR, the wide string counterpart is missing from AEEStdLib.h so that it was implemented from scratch.

Point 2 is less obvious. For example, a constructor such as

template <class U>   BrewString( const U& rhs , UINT sz = 0);

might take any type as input and we have to differentiate and act accordingly. On top of this, there is an additional problem: Conversion from one type to the other requests memory to be allocated and, more important, released—definitely not the end user’s responsibility.

The answer is a private internal class that acts like a proxy/auto pointer:

class Wrapper{public:   template <typename U>   Wrapper(U* t) : t_(getArray(t))   {}   template <typename U, typename R>   Wrapper(const BrewString<U,R>< t) : t_(getArray(t))   {}   ~Wrapper()   {     if (owns_)     {       delete[] t_;     }   }   const T* operator()() const   {     return t_;   }private:   template <typename U>   T* getArray(const U* c)   {     owns_ = true;     return P::createBuffer(c);   }   T* getArray(const T* u)   {     owns_ = false;     return const_cast<T*>(u);   }   template <typename U, typename R>   T* getArray(const BrewString<U,R>& u)   {     owns_ = false;     return getArray(u.toCharArray());   }   T* getArray(const BrewString<T,P>& u)   {     owns_ = false;     return const_cast<T*>(u.toCharArray());   }private:   T* t_;   bool owns_;};

At creation time, Wrapper simply takes a type, does some “type laundering” if needed, and establishes whether there is any memory to be owned and released at destruction time. Its use is based on the “resource acquisition is initialization” idiom, such as:

template <class U>BrewString( const U& rhs , UINT sz = 0): pData_(0){   Wrapper p(rhs);   CopyCtorImpl(p(), sz);}

Please note the minimal impact on BrewString: rhs is “wrapped” and p functor is used instead of rhs—very mechanical.

String is defined as:

typedef BrewString<char, BrewStringPolicy<char> > String;

and WString as:

typedef BrewString<AECHAR, BrewStringPolicy<AECHAR> > WString;

We can write now code like this:

WString w("br");w += "ew";const AECHAR* ae = w.toCharArray();String d ( w);d += ae;String y;y.ensureCapacity(20);y = "mybrew";WString tst(y);bool b = y.equals("brew");b = y.startsWith("my");

Final Remarks

Another goal of this implementation was to support both old and new compilers. This influenced some design decisions and added a certain “roughness”—especially in automatic type conversions.

The creation of temporaries is, as usual, reduced to a minimum. As p() calls are typically inlined for the trivial, case efficiency shouldn’t be affected.

This release contains some previously unpublished run-time allocation safety features (see setBuffer(), increaseBuffer(), etc.). Please consider this as an example only as different strategies are possible in this area. For example the new gcc compiler support for exceptions (and implicitly for throwing new) might change this radically.


Download the application — 211 Kb.


1 Utility Libraries for BREW—A String Class

About the Author

Radu Braniste is Director of Technology at Epicad. He can be contacted at [email protected]

# # #

Latest Posts

Related Stories