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.
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
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
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
Inheriting from
NonCopyable
might be more elegant. ButMOZ_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 implementingMOZ_DELETE
, could be served by an inheriting trick, butMOZ_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 haveJSRope
(strings representing the concatenation of two substrings) andJSLinearString
(strings whose characters occupy a linear range of memory). And there are further distinctions underneath that. AJSLinearString
is always aJSString
, but the inverse isn’t necessarily true: it might be aJSRope
instead.Given a
JSString
, you can ask if it is aJSLinearString
by callingstr->isLinear()
. Methods likestr->asLinear()
perform (debug-checked) downcasts fromJSString
toJSLinearString
and similar. Lastly, methods likestr->ensureLinear(cx)
attempt to convert a string to a more-derived form. (For example, ifstr
is aJSRope
,str->ensureLinear(cx)
tries to modify the structure ofstr
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 aJSLinearString
namedlstr
, 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 aJSLinearString
to aJSLinearString
, or ensure that aJSLinearString
was aJSLinearString
. 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 predatedMOZ_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 withbool
as an argument, but never withint
as an argument. (Maybe you’d rather see an expliciti != 0
in that case.) You could implement this with deleted functions: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, anddelete
made it a compile error if thatisLinear
were ever used: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
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:
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