Symbian Developer Network

 

Documentation and Code

EUserHL Core Idiom Library

Downloads

See An Introduction to L Classes for further documentation about the LString class.

The EUserHL package is available for all phones running Symbian OS v9.x. For version 1.2 (June 2009), LString16 & LString8 directly support C++ string literals.

Overview

The new EUserHL Core Idioms library delivers:

  • LString, a string class that handles its own buffer management and cleanup and whose parameters take natural C++ string literals
  • LCleanedupX and LManagedX, a set of cleanup management helper templates
  • CONSTRUCTORS_MAY_LEAVE, a helper macro that enables single-phase construction
  • OR_LEAVE, a helper macro to cleanly convert error-returning code into leaving code.

The necessary header files for exploiting the new code idioms library are supplied in a header EUserHL.h, supported by a DLL, EUserHL.DLL, and an EUserHL.SIS installable package, available for all Symbian OS v9-based devices.

This makes Symbian OS easier for programmers by:

  • Making it much easier and cleaner to write correct cleanup-safe code, with fewer lines of code than before.
  • Making an elegant, leave-safe implementation of the widely-used C++ RAII [R1] idiom available for Symbian OS programming.
  • Making it much easier and cleaner to write code involving arbitrary-length strings without choosing magic numbers for their length, and variable-length strings without performing manual memory management.

Using the Core Idioms library has a pervasive impact on line-of-code count and on simplicity and cleanness. That’s great when you write the code, and awesome when you come to maintain it.

The Core Idioms library delivers these improvements by exploiting the mapping of Symbian OS User::Leave() onto C++ throw, introduced in Symbian OS v9 and by relieving the application programmer of much explicit responsibility for memory management for strings and cleanup.

Idioms define the style by which programmers use an OS, and therefore have a pervasive ease-of-use impact in normal Symbian programming by programmers working at all levels of the software stack.

Use of this library is recommended for all new code. Only if you know you can do better by managing your own memory with traditional descriptor APIs, cleanup stack idioms, and two-phase construction, should you continue to use the traditional Symbian OS features instead of Core Idioms.

Before and after

Here’s how the Core Idioms library makes a difference in real-life situations:

Without Core Idioms library With Core Idioms library
 {
 TBuf<KMaxQuery> query; // fixed worst-
                        // case max
 query.Format(KQueryFormat, param);
 ExecuteQueryL(query);
 }

 {
 LString query;    // can grow its heap 
                   // buffer on demand
 query.FormatL(KQueryFormat, param);
 ExecuteQueryL(query);
 } // query buffer released on 
   // normal scope exit or leave
 {
 HBufC* queryc = 
     HBufC::NewLC(KTooBigForStackMaxQuery);
 TPtr query(queryc->Des());
 BuildQueryL(query);
 ExecuteQueryL(query);
 CleanupStack::PopAndDestroy();
 }
 {
 LString query;
 BuildQueryL(query);
 ExecuteQueryL(query);
 }



 {
 RBuf query;
 query.CleanupClosePushL();
 query.CreateL(TooBigForStackMaxQuery);
 BuildQueryL(query);
 ExecuteQueryL(query);
 CleanupStack::PopAndDestroy();
 }
 {
 LString query;
 BuildQueryL(query);
 ExecuteQueryL(query);
 }



 {
 CQuery* query = CQuery::NewL();
 CleanupStack::PushL(query);
 query->BuildQueryL();
 query->ExecuteQueryL();
 CleanupStack::PopAndDestroy();
 }
 {
 LCleanedupPtr<CQuery> query(CQuery::NewL());
 query->BuildQueryL();
 query->ExecuteQueryL();
 } // query deleted on normal scope 
   // exit or leave

 {
 CQuery* query = CQuery::NewL();
 CleanupStack::PushL(query);
 query.BuildQueryL();
 CleanupStack::Pop();
 return query;
 }
 {
 LCleanedupPtr<CQuery> query(CQuery::NewL());
 query->BuildQueryL();
 return query.Unmanage(); 
    // query was protected until Unmanage()
 }  // was called

 {
 RQuery query;
 CleanupClosePushL(query);
 query.BuildQueryL();
 query.ExecuteQueryL();
 CleanupStack::PopAndDestroy();
 }
 {
 LCleanedupHandle<RQuery>;
 query->BuildQueryL();
 query->ExecuteQueryL();
 } // query is closed on normal scope 
   // exit or leave

As this shows, the code with Core Idioms is usually one or two lines shorter than pre-Core Idioms code, and is never longer.

Cleanup stack operations don’t appear explicitly in the Core Idioms code, even though the cleanup stack is in fact being used, and all of the code shown here – both with and without Core Idioms – is totally leave-safe.

The code without Core Idioms often uses more memory than is really necessary, either on the stack or the heap, and the only way to avoid this is to code awkward extra lines of code. The code with Core Idioms uses only as much space as necessary and is brief.

Improvements in Core Idioms v1.2

Core Idioms v1.2 removes the need to specify string literals with _LIT, when using LString.

Core Idioms prior to v1.2 Core Idioms v1.2
 {
 // bunch of literal descriptors
 _LIT(KStringOne, "One ");
 _LIT(KStringTwo, "Two ");
 _LIT(KStringThree, "Three");
 // somewhat later, use those literals
 LString s;
 s = KStringOne;
 s.AppendL(KStringTwo);
 s += KStringThree;
 }
 {




 // just use wide literals from pure C++
 LString s;
 s = L"One ";
 s.AppendL(L"Two ");
 s += L"Three";
 }

This feature exploits the fact that expressions such as L"Three" produce a wchar_t[], and that in all ABIs of interest to Symbian OS, wchar_t is a 16-bit wide character, compatible with LString16.

Using Core Idioms

The EUserHL Core Idioms library provides new APIs for simpler, exception-safe programming, a new string class and single-phase object construction.

Self-managing string class

The LString class provides a self managing, resizable descriptor that bridges the gap between the behavior of standard C++ strings, such as std::string, and Symbian descriptors.  LString is recommended instead of traditional TBuf, HBufC and RBuf descriptors.  LStrings can be passed into Symbian OS APIs which take TDes& and const TDesC& parameters.

The Symbian C++ descriptors have been a defining attribute of Symbian OS. The descriptor family was designed with reliability and space efficiency as primary goals. This was achieved by spreading common string behavior across a number of descriptor classes. Unfortunately, this decision has a usability cost due to the number of classes that must be directly understood by developers to achieve common objectives with strings. LString is intended to provide a self-managing alternative to several of the standard descriptor types.

An LString may be used much like a simple T class. LString-typed variables will automatically clean up after themselves when they go out of scope, and LString-typed fields will similarly automatically release all owned resources when their containing object is destroyed.

In addition to the value-type semantics described above, LString also supports automatic in-place resizing. All standard descriptor methods are available but for any non-leaving descriptor method that may panic due to buffer overflow, LString adds a corresponding leaving method that automatically expands the underlying buffer on-demand. For example, Append() will panic if the new data overflows available buffer space, while AppendL() will attempt to realloc() the buffer as necessary. The new leaving variants may therefore leave with KErrNoMemory, may invalidate any existing raw pointers into the data buffer (e.g., those previously returned by Ptr()), and may change the value returned by MaxLength(). To protect developers from inadvertently calling the non-leaving methods on LString, these have been privatized.

LString is compatible with existing APIs that accept const TDesC& and TDes& arguments. Both 8 and 16 bit versions are provided.

Finally, to allow LString to be allocated on the heap and deleted, the class defines implementations for various overloads of the operator new() and operator delete() methods.

For more information about the new string class, see An Introduction to L Classes.

Class templates to automate cleanup

The LCleanedupX and LManagedX class templates from Core Idioms are Symbian-specific analogues to C++ smart pointers:

  • TheLCleanedupX templates allow leave-safe objects to be declared, which are placed automatically on the Symbian OS cleanup stack. These objects are then cleaned up automatically, whether processing terminates normally or leaves.
  • The LManagedX templates are intended for exception-safe objects to be declared as members of classes. These objects are then cleaned up automatically, whether processing terminates normally or leaves. A key benefit is that this permits single-phase construction of CBase-derived objects.

The LCleanedupX and LManagedX class templates are:

LCleanedupX template

LManagedX template

What it manages

How it cleans up by default

LCleanedupPtr

LManagedPtr

a pointer

deletes the pointer

LCleanedupHandle

LManagedHandle

a handle, by value

calls Close() on the handle

LCleanedupRef</p>

LManagedRef

a handle, by reference

calls Close() on the handle

LCleanedupArray

LManagedArray

a C array

deletes the array

LCleanedupGuard

LManagedGuard

anything protected with a TCleanupItem

calls the TCleanupItem’s cleanup operation

The cleanup strategy can be changed to one of the predefined cleanup strategies provided, or to a user-defined custom cleanup strategy. For more information about the cleanup management helper templates, see An Introduction to L Classes.

LCleanedupX templates

Use LCleanedUpX templates instead of the classic CleanupStack::PushL() and CleanupStack::PopAndDestroy() approach to write leave-safe code more succinctly and elegantly than was previously possible, as shown in the before-and-after examples above.

Using:
{
LCleanedupPtr<CQuery> query(CQuery::NewL()); 
// ...
if (condition)
    return;  
// ...
}
has a similar behaviour to:
{
CQuery* query =  CQuery::NewL();
CleanupStack::PushL(query);
//...
if (condition)
    {
    // Pop and destroy the object (also deletes the pointer)
    CleanupStack::PopAndDestroy(query);
    return;  
    }	
// ...
// Pop and destroy the object (also deletes the pointer)
CleanupStack::PopAndDestroy(query);
}

LManagedX classes

Use LManagedX templates for member variables in classes which implement single-phase construction. See the example below.

Do not use LManagedX for any other purpose, without being aware of the differences between C++ exception-handling semantics and Symbian OS leave semantics: [R2]

  • LManagedX templates, and conventional C++ exception safety, rely on standard unwinding of the C++ program stack when an exception, including a Symbian OS leave, occurs. This unwinding of the program stack causes destructors to be invoked in a sequence specified by the C++ language.
  • LCleanedupX templates, and classic Symbian OS cleanup stack operations, rely on the unwinding of the Symbian OS cleanup stack when a User::Leave() occurs. Although User::Leave() generates a C++ exception, the cleanup stack is not unwound when a C++ exception is thrown any other way. Also, User::Leave() processing unwinds the Symbian OS cleanup stack first, and then throws the C++ exception which unwinds the C++ program stack.
These differences can create counter-intuitive effects if LManagedX objects are used other than as recommended.

Single-phase construction for CBase-derived classes

The Core Idioms library supports single-phase construction of CBase-derived classes. To implement single-phase construction,

  • specify the CONSTRUCTORS_MAY_LEAVE macro in your class declaration
  • encapsulate members which must be cleaned up if a constructor leaves, in LManagedX templates
Here’s how:
  #include <e32std.h>
  #include <f32file.h>
  #include <euserhl.h> // Core  Idioms
  
  class CFinder : public CBase
      {
  public:
      // We have opted to use single-phase  construction here, and some of 
      // our constructor's initialization  actions may leave. In order to 
      // guarantee full cleanup in all cases,  we have to declare this fact.
      CONSTRUCTORS_MAY_LEAVE
      static CFinder* NewL(const TDesC&  aPattern);
      ~CFinder();
      void GetNextMatchL(TDes& aMatch);
  protected:
      CFinder(const TDesC& aPattern);
  protected:
      LString iPattern; // looks after its own cleanup
      LManagedHandle<RFs> iFs; // will be closed as required
      // ...
      };

  CFinder* CFinder::NewL(const  TDesC& aPattern)
      {
      return new(ELeave) CFinder(aPattern);
      }

  CFinder::CFinder(const TDesC&  aPattern) 
      // This initializer may leave, since the LString will allocate a
      // heap buffer large enough to contain a copy of aPattern's data
      : iPattern(aPattern) 
      {
      // If connection fails and we leave here, iPattern's destructor
      // will be called automatically, and the string's resources will
      // be released
      iFs->Connect() OR_LEAVE;
      }

  CFinder::~CFinder()
      {
      // Automatic destruction of each of the data members does all 
      // of the work for us: iPattern's heap buffer if freed, while 
      // Close() is called on the managed RFs in iFs.

// Even though this destructor is textually empty, it should // still be exported; the compiler is generating destruction // logic for us in this case }

Compared to two-phase construction, NewL() is not implemented in terms of NewLC() followed by CleanupStack::Pop(); and there’s no ConstructL().

In fact, the NewL() function really isn’t necessary at all. Its sole benefit is the possibility to change the implementation to old-fashioned two-phase construction without breaking compatibility. Without a NewL(), the implementation would be even briefer and clients would just call new(ELeave) CFinder(pattern).

Note the use of the pointer operator rather than the member-selection operator. If iFs had been an unmanaged RFs, we’d have used iFs.Connect(). But since iFs is an LManagedHandle<RFs>, we use iFs->Connect().

Macro to cleanly convert error-returning code into leaving code

In the example above, the line

        iFs->Connect() OR_LEAVE;

shows the Core Idioms OR_LEAVE macro, which uses C++ operator overloading to generate code which is exactly equivalent to

        User::LeaveIfError(iFs->Connect());

With OR_LEAVE, it’s just as efficient, and much more readable.

Conclusion

The EUserHL Core Idioms library allows:

  • Experienced Symbian C++ developers to write robust and compact string-handling code with semi-automated exception handling.
  • New Symbian C++ developers to use Symbian OS exceptions, the cleanup stack and descriptors more easily, with fewer programming errors and more rapid application development.

References

[R1] Resource Acquisition Is Initialization: a design pattern popular in many object oriented languages, which combines the acquisition and release of resources with initialization and uninitialization of objects. See en.wikipedia.org/wiki/RAII.