09.11.11

Introducing MOZ_DELETE, a macro improving upon the deliberately-unimplemented method idiom

C++ default operators and the sole-ownership idiom

Often a C++ class will solely manage some value: for example, a GtkWindow* or a void* for malloc‘d memory. The class will then release ownership in its destructor as appropriate. It would be extremely problematic to release ownership multiple times — think security-vulnerability-problematic. C++ copy construction and default assignment exacerbate this issue, because C++ automatically generates these methods for all classes, even when the default behavior breaks sole-ownership. The C++98 idiom solving this is to privately declare a copy constructor and a default assignment operator, then never define them:

struct Struct
{
  private:
    Struct(const Struct& other);
    void operator=(const Struct& other);
};

Declaring the methods privately prevents any code but friends of Struct from calling them. And by never defining them, even such friends will cause a link-time error if they try.

Disabling the default operators in C++11

Once you’re familiar with this idiom it’s not too bad. But initially, it’s pretty unclear. And nothing prevents someone from actually defining these methods. (They could only be used by Struct or friends of Struct, to be sure, but for sufficiently complex code it’s possible someone might make a mistake.) C++11 improves upon this trick by introducing deleted function syntax:

struct Struct
{
  private: // no longer necessary, but doesn't hurt
    Struct(const Struct& other) = delete;
    void operator=(const Struct& other) = delete;
};

Deleted functions are effectively removed from name lookup: using, defining, or referring to a deleted function produces a compile error — far better than a link error or, even worse, no error at all.

= delete support in mfbt

The Mozilla Framework Based on Templates now includes support for declaring a function only to prevent its use (or use of an inherited version). The MOZ_DELETE macro encapsulates this support:

#include "mozilla/Types.h" // MOZ_DELETE has since moved...
#include "mozilla/Attributes.h" // ...to here

struct Struct
{
  private:
    Struct(const Struct& other) MOZ_DELETE;
    void operator=(const Struct& other) MOZ_DELETE;
};

MOZ_DELETE isn’t as readable or understandable as = delete, but it’s searchable, and the comment next to its definition will clarify matters. If the declarations are private, MOZ_DELETE is just as good as the traditional idiom, and in compilers supporting C++11 deleted functions it’s better.

Which compilers support C++11 deleted functions? I’m aware of GCC since 4.4, Clang since 2.9, and ICC since 12.0. Rightly, if unfortunately, you must specify -std=c++0x or similar to use deleted function syntax without causing a warning. For various reasons Mozilla can’t do that yet, so MOZ_DELETE only produces the C++11 syntax when compiling with Clang (where we can pass -Wno-c++0x-extensions to disable the warning). I’d love to see it use C++11 syntax in GCC and ICC as well, but I don’t have the time to solve the -std=c++0x problem now, or to figure out another workaround. I’ve filed bug 701183 for this problem — help there is much appreciated.

Summary

Use MOZ_DELETE when declaring any method you will intentionally not implement. It’ll work better, and produce better errors, in some compilers. Those compilers don’t include GCC or ICC yet, but with your help they could. Any takers?

Update, evening of November 10, 2011: I just landed further changes to make MOZ_DELETE use C++11 syntax with GCC when compiling with -std=c++0x (which we apparently do more often than I’d thought), so you should now get its goodness in GCC as well — most of the time. In some “exotic” situations we don’t compile anything with -std=c++0x, so you won’t get any benefit there. Also, the JavaScript engine is never compiled with it. So if you want this to work fully, everywhere, you should use Clang.

5 Comments »

  1. It looks like the comment you link to in the patch on mozilla-inbound has its errors confused. “An attempt to use the method will produce an error *at link time*, not at compile time, in compilers for which this macro can be implemented,” sounds like it should be the other way around. The comment following the example (if MOZ_DELETE cannot be implemented as “= delete”) appears to be correct.

    Comment by Patrick O'Leary — 09.11.11 @ 15:50

  2. Ah, yes, I did switch these around. Thanks for pointing it out! I’ll get a fix in for this.

    Comment by Jeff — 10.11.11 @ 02:21

  3. Personally, I think inheriting from a class noncopyable is more elegant. Is there some reason you dislike that option?

    Comment by bastiaan — 12.11.11 @ 02:53

  4. Inheriting from NonCopyable might be more elegant. But MOZ_DELETE isn’t useful for just that purpose: it’s also a poor-man’s version of name hiding. The following example, which was the actual motivator for my implementing MOZ_DELETE, could be served by an inheriting trick, but MOZ_DELETE is clearly superior. (Apologies for the example length in advance.)

    Strings in SpiderMonkey form a minimal hierarchy. At top we have JSString: any string. Underneath that we have JSRope (strings representing the concatenation of two substrings) and JSLinearString (strings whose characters occupy a linear range of memory). And there are further distinctions underneath that. A JSLinearString is always a JSString, but the inverse isn’t necessarily true: it might be a JSRope instead.

    Given a JSString, you can ask if it is a JSLinearString by calling str->isLinear(). Methods like str->asLinear() perform (debug-checked) downcasts from JSString to JSLinearString and similar. Lastly, methods like str->ensureLinear(cx) attempt to convert a string to a more-derived form. (For example, if str is a JSRope, str->ensureLinear(cx) tries to modify the structure of str such that the characters occupy a linear range of memory.)

    All these methods were exposed through JSString by inheritance. This made for some odd possibilities. If you had a JSLinearString named lstr, you could ask it if it were linear, even tho the question would be tautological: lstr->isLinear() would by definition be true. You could also meaninglessly cast a JSLinearString to a JSLinearString, or ensure that a JSLinearString was a JSLinearString. None of these operations make any sense! Such vacuous actions and queries lead to confusion, and they may pointlessly consume processor time. To prevent mistakenly-pointless operations like these, I wanted to make it impossible to make them. Deleting them from the more-derived subclasses addressed this problem nicely. (That revision predated MOZ_DELETE; when I landed it, I also added it to all the locations in the string patch that would have used it, had it been available.)

    Deleted functions present other interesting uses. For example, you can use them to prevent undesired implicit conversions from occurring. Say you have a class with a set method that you want to be called only with bool as an argument, but never with int as an argument. (Maybe you’d rather see an explicit i != 0 in that case.) You could implement this with deleted functions:

    class A
    {
        bool b;
      public:
        A() : b(false) { }
        void set(bool b) { this->b = b; }
        void set(int) = delete;
    };
    

    It’s perhaps worth noting that =delete is great for making a function unusable, but it’s not so great for name hiding (the original use case I had!). For name hiding you’d like assurance that the name you’re deleting is present in a superclass. =delete doesn’t give you that. Earlier this year C++11 (before it became final) had syntax to do this. In the example below, new made it a compile error if the method didn’t hide a name in a superclass, and delete made it a compile error if that isLinear were ever used:

    // Not valid C++11, but was once in a draft version
    class JSLinearString : public JSString
    {
      bool isLinear() const new = delete;
    };
    

    Unfortunately this feature got voted out of the final standard (perhaps amusingly, because the standards committee found they were running out of places to put the new modifier, for members which were not functions), so second-best became simply = delete.

    Anyway. To make a long story short, MOZ_DELETE has more uses than just killing off copy-construction and default assignment.

    Comment by Jeff — 12.11.11 @ 04:00

  5. I’ve learned another reason to not simply inherit from a noncopyable class, which you might be interested in knowing about: ABI. Apparently some architectures don’t like the non-copyable idiom in some hideous edge cases. See this entry from WebKit’s changelog:

    r68414 | andersca@apple.com | 2010-09-27 15:18:17 -0400 (Mon, 27 Sep 2010) | 18 lines
    
    Add WTF_MAKE_NONCOPYABLE macro
    https://bugs.webkit.org/show_bug.cgi?id=46589
    
    Reviewed by Alexey Proskuryakov and Adam Barth.
    
    Going forward, we'd like to get rid of the Noncopyable and FastAllocBase classes. The
    reason for this is that the Itanium C++ ABI states that no empty classes of the same type
    can be laid out at the same offset in the class. This can result in objects getting larger
    which leads to memory regressions. (One example of this is the String class which grew by
    sizeof(void*) when both its base class and its first member variable inherited indirectly
    from FastAllocBase).
    

    WebKit used to inherit from a noncopyable class to implement hidden copy/assignment constructors but no longer does, apparently for the reason stated above.

    Comment by Jeff — 16.12.11 @ 09:46

RSS feed for comments on this post. TrackBack URI

Leave a comment

HTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>