A few good C++ coding practices for Mozilla

XPCOM

Use nsCOMPtrs

Use nsCOMPtrs when you need to refcount - both for local variables and for member variables. See the nsCOMPtr User's Manual for more details.

Don't bother writing QueryInterface, AddRef, and Release manually

Don't bother writing the standard forms of QueryInterface, AddRef, and Release manually. It just introduces possibility of error and makes it harder for us to switch to table-driven QueryInterface if we eventually decide to do so (or, more likely, your class will just miss out). There are macros in nsISupportsUtils.h that implement these functions for you. To implement these three functions for an object nsBlah implementing three XPCOM interfaces, nsIFoo, nsIBar, and nsIBaz, where a QueryInterface to nsISupports should give the pointer to the nsIFoo, use:

NS_IMPL_ISUPPORTS3(nsBlah, nsIFoo, nsIBar, nsIBaz)
If you want some of these but not the others, this can be split into:
NS_IMPL_QUERYINTERFACE3(nsBlah, nsIFoo, nsIBar, nsIBaz)
NS_IMPL_ADDREF(nsBlah)
NS_IMPL_RELEASE(nsBlah)
and the QueryInterface can be split even further into
NS_INTERFACE_MAP_BEGIN(nsBlah)
  NS_INTERFACE_MAP_ENTRY(nsIFoo)
  NS_INTERFACE_MAP_ENTRY(nsIBar)
  NS_INTERFACE_MAP_ENTRY(nsIBaz)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFoo)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(nsBlah)
NS_IMPL_RELEASE(nsBlah)

There are also similar macros for an inherited implementation (where the AddRef, Release, and the tail of the QueryInterface methods forward to a base class. These are needed when the derived class implements additional interfaces that the base class does not. These macros are (for small n) NS_IMPL_ISUPPORTS_INHERITEDn, which can be split up into NS_IMPL_QUERY_INTERFACE_INHERITEDn, NS_IMPL_ADDREF_INHERITED, and NS_IMPL_RELEASE_INHERITED.

There are also similar macros (insert _THREADSAFE after NS_IMPL) for implementing these three functions in a threadsafe manner. This means that reference counting is done in a threadsafe manner and that the QueryInterface is hacked in DEBUG builds to allow the NS_VERIFY_THREADSAFE_INTERFACE macro to work. (There aren't macros for a threadsafe inherited implemenation since the normal inherited implementation macros are sufficient.)

Note: these macros may not work well when writing XPCOM components that do not link against XPCOM and/or are intended to be used with multiple versions of XPCOM, i.e., components that are not part of the regular Mozilla build.

Avoid IIDs for typesafety

IIDs introduce a huge potential for type safety violations, since they require the programmer to specify the same thing twice (in the type of a variable, and in an IID) without having the compiler check that the two are the same. However, some neat tricks in function templates in XPCOM exist so that you should almost never need to manually deal with IIDs in your code. (That is, you should never have to type NS_GET_IID or NS_DEFINE_IID, and the presence of these macros is a sign that code is not typesafe.)

Rather than manually calling functions such as QueryInterface, QueryReferent, GetInterface, CreateInstance, and GetService, you should use one of two typesafe alternatives:

  1. If you're using nsCOMPtrs, which you should be in most cases, use the helpers do_QueryInterface, do_QueryReferent, do_GetInterface, do_CreateInstance, do_GetService that determine the correct IID from the type of the nsCOMPtr. For example:
    nsCOMPtr<nsIDOMDocument> domDoc( do_QueryInterface(mDocument) );
    nsCOMPtr<nsIDOMElement> elt( do_QueryInterface(aNode, &rv) );
    nsCOMPtr<nsIPrefService> pref( do_GetService(NS_PREF_CONTRACTID, &rv) );
  2. If you're not using nsCOMPtrs, which is reasonable for things like out parameters or static variables, use the template functions CallQueryInterface, CallQueryReferent, CallGetInterface, CallCreateInstance, or CallGetService, which determine the correct IID from the type of the last parameter. For example:
    return CallQueryInterface(node, aResult);
    rv = CallCreateInstance(kStyleSetCID, aResult);
    CallGetService("@mozilla.org/js/xpc/RuntimeService;1", &gJSRuntimeService);

Be careful of ownership

Pay attention to where you use owning pointers as member variables. It's very easy to create ownership cycles that cause all of the objects in the cycle and everything else they own to leak. If you think you can break the cycles, think again: are you breaking the cycles from a destructor? from something called from a destructor? Most hard-to-fix leaks come from ownership cycles that programmers thought they could break reliably.

See scc's COM Ownership Guidelines for more information.

Miscellaneous

Strings

The string classes in Mozilla to help avoid various problems that often arise when writing string-handling code from scratch, such as buffer overruns. However, even more importantly, the new string classes are designed to allow buffer ownership models that avoid copying (which can significantly hurt performance). (This will be even more true once copy-on-write sharable strings work.) The important parts of the current string hierarchy look like this, with the same structures existing for one-byte strings with CString in place of String (except for nsCAutoString):

                        nsAString
                        /      \
     nsASingleFragmentString    `-------------------------.
                    /      \                               \
            nsAFlatString   \                        nsAPromiseString
              /  \  \  \    nsSingleFragmentDepende-     /      \
             /    \  \  `------.          ntSubstring   /        \
            /      \  `-------. \   nsDependentConcatenation   nsDependentSubstring
            /       `-.        \ `------------------------.
           /           \        \                          \
   nsSharableString  nsString  nsDependentString  nsPromiseFlatString
         |              |               \  
   nsXPIDLString    nsAutoString      NS_LITERAL_STRING.
                                    NS_NAMED_LITERAL_STRING

although in the future they will probably look something more like this:

                        nsAString
                        /      \
                       /        `---------------------------------.
             nsASingleFragmentString            \                  \
                     /     \           nsDependentConcatenation  nsDependentSubstring
                    /       `---------------.
              nsAFlatString                  \
                   |    \ \           nsSingleFragmentDependentSubstring
                   |     \ `-------.
        nsSharableString  `-----.   \
            /           \        \   nsPromiseFlatString
           /           nsString  nsDependentString 
          /               |               \  
   nsXPIDLString      nsAutoString      NS_LITERAL_STRING,
                                     NS_NAMED_LITERAL_STRING

Abstract string classes
nsAString
This is the abstract string type. An nsAString represents a sequence of characters that may be stored in multiple fragments and need not be null-terminated.
nsAFlatString
An nsAFlatString is an abstract type for "flat" strings, i.e., strings that are a single fragment and are null-terminated. All implementations of nsAFlatString have a get method to return a const PRUnichar*.
nsASingleFragmentString
nsASingleFragmentString is an abstract type describing a string that is a single fragment but is not necessarily null-terminated. The result of calling Substring on any single fragment string is also a single fragment string.
Commonly used string classes
nsString
nsString is a general concrete string class. It allocates storage on the heap. In the future, it will use copy-on-write reference counted buffers. The current implementation is obsolete (and has a number of serious problems, including excessive indirection in non-inlined function calls significant enough to seriously hurt performance), but there will eventually be a new implementation by the same name (although probably with some of the member functions removed) in the new string hierarchy.
nsAutoString
nsAutoString is like nsString, except it has a 64 character buffer internal to the object so that it can avoid allocation when the string remains less than 64 characters. It is meant to be used on the stack, where the extra size is not a serious bloat problem and where the lack of allocation can reduce the required number of allocations (which are expensive due to locking) from 1 to 0 rather than from 2 to 1.
nsSharableString
nsSharableString is the base sharable string class, for copy-on-write buffers. Its current implementation is incomplete and a number of methods do not yet work. It may eventually be renamed to nsString.
nsXPIDLString
nsXPIDLString (in the new world -- there was once an obsolete implementation of it) is like an nsSharableString except that it has a mechanism for assigning to it through assignment to a PRUnichar** parameter to a function. getter_Copies(my_XPIDLString) gives a PRUnichar** that can be filled in with a buffer.
nsDependentString
nsDependentString wraps an existing character buffer as an nsAFlatString, without copying. The life of the nsDependentString should never extend past the life of the buffer on which it is dependent.
NS_LITERAL_STRING, NS_NAMED_LITERAL_STRING
NS_LITERAL_STRING and NS_NAMED_LITERAL_STRING are macros that create a string that is dependent on a string in the text segment of the program. (For the 2-byte versions, on platforms that do not support L"" strings or where wchar_t is not (or cannot via a compiler option) a 2-byte unsigned character, this is equivalent to the more expensive NS_ConvertACSIItoUCS2.)
String classes that generally shouldn't appear in code
nsDependentConcatenation
nsDependentConcatenation is a multi-fragment string class that is the result of using operator+ on two nsAStrings. Thus building a string operator+ is more efficient than building up a string using Append since it does not copy, and even if a copy is required it can be more efficient to use operator+ since it will prevent the need for reallocation.
nsDependentSubstring
nsDependentSubstring is the result of the Substring function. Using Substring is more efficient than using nsAString::Mid, nsAString::Left, or nsAString::Right since it does not copy. The signatures of the latter three functions may at some point in the future be changed to encourage more efficient style.
nsPromiseFlatString
nsPromiseFlatString is the result of a call to PromiseFlatString(nsAString&). It provides access to a flat string (i.e., one that is single-fragment and null-terminated) whether or not the underlying string is flat. If the underlying string is flat, the underlying buffer is simply returned, but if it is not flat, a new buffer is created and the string is copied into that buffer, which is destroyed once the nsPromiseFlatString object goes away.

Use abstract parameter types

For functions that take strings as parameters (including those that modify strings), use an appropriately abstract parameter type. Generally this means using const nsAString& or nsAString&, but in some cases it may be preferable to use one of the other abstract types for performance reasons. (There are currently some member functions of nsString that don't have good replacements. Needing these functions provides a legitimate reason to use const nsString& and nsString& parameters for the time being.)

Use iterators well

When using iterator variables, declare the types as the iterator or const_iterator type of the string type that the iterator is iterating over. For example, when iterating over a string that is statically typed as an nsAFlatString, use nsAFlatString::const_iterator.

To loop over the characters in a string, it is inefficient to simply use the pattern for ( ...; iter != iter_end; ++iter) { ... } since the operator++ on iterators must check every time to see whether the current position is the last one in a fragment. Instead, it is better to use two nested loops, where the outer loop checks iter.size_forward() and the inner loop iterates over the number of characters returned and then the outer loop advances by that number of characters. It is also possible to accomplish the same thing in a cleaner way with a character sink and the |copy_string| function.

Use NS_LITERAL_[C]STRING

If you want a string object that always contains the same text, don't use ns[C]AutoString, use NS_LITERAL_[C]STRING, which doesn't copy (or convert), except on platforms where wchar_t is not 2-byte unsigned (most Unix platforms, but not newer gcc's).

Use Substring

If you want a string object that represents a substring of a string, don't copy it into another string, use Substring. There are two variants of Substring: Substring( string, start-position, length) and Substring( begin-iterator, end-iterator). (As always, with a range of characters, the begin iterator points to the first character that is part of the substring and the end iterator points to the first character after the end of the substring.)

Prefer operator+ to Append

Rather than assigning to a string with multiple calls to Append, use a single call to Assign and operator+. Since the result of operator+ on strings is a temporary promise substring, this pattern allows the final buffer to be given the correct size in a single allocation and it avoids the copying that must happen when growing the buffer.

Do conversions with minimum copying

Be aware that (at least for now) the functions NS_ConvertASCIItoUCS2, NS_ConvertUCS2toUTF8, NS_ConvertUTF8toUCS2, and NS_LossyConvertUCS2toASCII are really objects derived from ns[C]AutoString and thus copy the buffer into a temporary buffer. If this buffer is then assigned to an ns[C][Auto]String, it will be copied again (at least until copy-on-write sharing works).

Use PromiseFlat[C]String with care

Since PromiseFlatString may (if the string it is given is not flat) need to create a temporary buffer that will go away when the promise object goes away, one must never use the string returned from PromiseFlatString after the promise string has been destroyed.
Bad example:

const PRUnichar *foo = PromiseFlatString(myString).get();
do_Something(foo);  // ERROR: foo could point to freed memory

Good example:
do_Something(PromiseFlatString(myString).get());

Now that gcc 2.7.2.3 is no longer supported, it is also possible to bind the temporary to a reference (which ensures that the lifetime of the temporary is as long as the lifetime of the reference, rather than just the lifetime of the statement) and do something like:

const nsAFlatString &foo = PromiseFlatString(myString);
do_Something(foo.get());
do_Something_Else(foo.get());

Be careful binding temporaries to references

[ Added 2002-05-14. ]

However, when you bind temporaries to references, you must be careful not to have any intermediate temporaries that are not bound to references that are needed by the bound temporary, because their lifetime is not affected by the binding of the temporary to the reference. This is unlikely to matter in the case above, with PromiseFlatString, as long as you don't use PromiseFlatString on a string that's already known to be flat, since PromiseFlatString flattens any non-flat string immediately, and you're unlikely to see a string of unknown flatness that's a temporary. However, it does matter when using operator+, since it creates a temporary for each call. Thus the following will fail:

const nsAFlatCString &fullPath =
    dirString + NS_LITERAL_CSTRING("/") + fileString;
do_Something(fullPath); // ERROR: The temporary created by the
                        // concatenation of dirString and
                        // NS_LITERAL_CSTRING("/") has already
                        // been destroyed, but fullPath depends
                        // on it (and on fileString).

Valid HTML 4.0!

(Back to Mozilla Stuff, David Baron)

LDB, dbaron@dbaron.org, 2001-07-07, 2001-09-29, 2001-10-08, 2002-05-14