From 8de8234de4ca3f13ae3bf794da5fe82b5fc2b1cc Mon Sep 17 00:00:00 2001 From: Sebastian Redl Date: Sun, 21 Dec 2008 11:56:24 +0000 Subject: [PATCH] Document how move semantics and the emulation work. Do this in-file because it's such a specific thing. Restructure code for less duplication. llvm-svn: 61308 --- clang/include/clang/Parse/Ownership.h | 318 +++++++++++++++----------- 1 file changed, 190 insertions(+), 128 deletions(-) diff --git a/clang/include/clang/Parse/Ownership.h b/clang/include/clang/Parse/Ownership.h index 36e9dc93294f..2e2a57d79fd3 100644 --- a/clang/include/clang/Parse/Ownership.h +++ b/clang/include/clang/Parse/Ownership.h @@ -14,6 +14,94 @@ #ifndef LLVM_CLANG_PARSE_OWNERSHIP_H #define LLVM_CLANG_PARSE_OWNERSHIP_H +// -------------------------- About Move Emulation -------------------------- // +// The smart pointer classes in this file attempt to emulate move semantics +// as they appear in C++0x with rvalue references. Since C++03 doesn't have +// rvalue references, some tricks are needed to get similar results. +// Move semantics in C++0x have the following properties: +// 1) "Moving" means transferring the value of an object to another object, +// similar to copying, but without caring what happens to the old object. +// In particular, this means that the new object can steal the old object's +// resources instead of creating a copy. +// 2) Since moving can modify the source object, it must either be explicitly +// requested by the user, or the modifications must be unnoticeable. +// 3) As such, C++0x moving is only allowed in three contexts: +// * By explicitly using std::move() to request it. +// * From a temporary object, since that object cannot be accessed +// afterwards anyway, thus making the state unobservable. +// * On function return, since the object is not observable afterwards. +// +// To sum up: moving from a named object should only be possible with an +// explicit std::move(), or on function return. Moving from a temporary should +// be implicitly done. Moving from a const object is forbidden. +// +// The emulation is not perfect, and has the following shortcomings: +// * move() is not in namespace std. +// * move() is required on function return. +// * There are difficulties with implicit conversions. +// * Microsoft's compiler must be given the /Za switch to successfully compile. +// +// -------------------------- Implementation -------------------------------- // +// The move emulation relies on the peculiar reference binding semantics of +// C++03: as a rule, a non-const reference may not bind to a temporary object, +// except for the implicit object parameter in a member function call, which +// can refer to a temporary even when not being const. +// The moveable object has five important functions to facilitate moving: +// * A private, unimplemented constructor taking a non-const reference to its +// own class. This constructor serves a two-fold purpose. +// - It prevents the creation of a copy constructor that takes a const +// reference. Temporaries would be able to bind to the argument of such a +// constructor, and that would be bad. +// - Named objects will bind to the non-const reference, but since it's +// private, this will fail to compile. This prevents implicit moving from +// named objects. +// There's also a copy assignment operator for the same purpose. +// * An implicit, non-const conversion operator to a special mover type. This +// type represents the rvalue reference of C++0x. Being a non-const member, +// its implicit this parameter can bind to temporaries. +// * A constructor that takes an object of this mover type. This constructor +// performs the actual move operation. There is an equivalent assignment +// operator. +// There is also a free move() function that takes a non-const reference to +// an object and returns a temporary. Internally, this function uses explicit +// constructor calls to move the value from the referenced object to the return +// value. +// +// There are now three possible scenarios of use. +// * Copying from a const object. Constructor overload resolution will find the +// non-const copy constructor, and the move constructor. The first is not +// viable because the const object cannot be bound to the non-const reference. +// The second fails because the conversion to the mover object is non-const. +// Moving from a const object fails as intended. +// * Copying from a named object. Constructor overload resolution will select +// the non-const copy constructor, but fail as intended, because this +// constructor is private. +// * Copying from a temporary. Constructor overload resolution cannot select +// the non-const copy constructor, because the temporary cannot be bound to +// the non-const reference. It thus selects the move constructor. The +// temporary can be bound to the implicit this parameter of the conversion +// operator, because of the special binding rule. Construction succeeds. +// Note that the Microsoft compiler, as an extension, allows binding +// temporaries against non-const references. The compiler thus selects the +// non-const copy constructor and fails, because the constructor is private. +// Passing /Za (disable extensions) disables this behaviour. +// The free move() function is used to move from a named object. +// +// Note that when passing an object of a different type (the classes below +// have OwningResult and OwningPtr, which should be mixable), you get a problem. +// Argument passing and function return use copy initialization rules. The +// effect of this is that, when the source object is not already of the target +// type, the compiler will first seek a way to convert the source object to the +// target type, and only then attempt to copy the resulting object. This means +// that when passing an OwningResult where an OwningPtr is expected, the +// compiler will first seek a conversion from OwningResult to OwningPtr, then +// copy the OwningPtr. The resulting conversion sequence is: +// OwningResult object -> ResultMover -> OwningResult argument to +// OwningPtr(OwningResult) -> OwningPtr -> PtrMover -> final OwningPtr +// This conversion sequence is too complex to be allowed. Thus the special +// move_convert functions, which help the compiler out with some explicit +// conversions. + namespace clang { // Basic @@ -133,17 +221,15 @@ namespace clang } template - class ASTOwningResult + class ASTOwningPtr { ActionBase *Actions; void *Node; - bool Invalid; - friend class moving::ASTResultMover; - friend class ASTOwningPtr; + friend class moving::ASTPtrMover; - ASTOwningResult(ASTOwningResult&); // DO NOT IMPLEMENT - ASTOwningResult& operator =(ASTOwningResult&); // DO NOT IMPLEMENT + ASTOwningPtr(ASTOwningPtr&); // DO NOT IMPLEMENT + ASTOwningPtr& operator =(ASTOwningPtr&); // DO NOT IMPLEMENT void destroy() { if (Node) { @@ -153,63 +239,34 @@ namespace clang } public: - typedef ActionBase::ActionResult::UID> DumbResult; - - explicit ASTOwningResult(ActionBase &actions, bool invalid = false) - : Actions(&actions), Node(0), Invalid(invalid) {} - ASTOwningResult(ActionBase &actions, void *node) - : Actions(&actions), Node(node), Invalid(false) {} - ASTOwningResult(ActionBase &actions, const DumbResult &res) - : Actions(&actions), Node(res.Val), Invalid(res.isInvalid) {} - /// Move from another owning result - ASTOwningResult(moving::ASTResultMover mover) - : Actions(mover->Actions), Node(mover->take()), Invalid(mover->Invalid) {} - /// Move from an owning pointer - ASTOwningResult(moving::ASTPtrMover mover); + explicit ASTOwningPtr(ActionBase &actions) + : Actions(&actions), Node(0) {} + ASTOwningPtr(ActionBase &actions, void *node) + : Actions(&actions), Node(node) {} + /// Move from another owning pointer + ASTOwningPtr(moving::ASTPtrMover mover) + : Actions(mover->Actions), Node(mover->take()) {} - /// Move assignment from another owning result - ASTOwningResult & operator =(moving::ASTResultMover mover) { + /// Move assignment from another owning pointer + ASTOwningPtr & operator =(moving::ASTPtrMover mover) { Actions = mover->Actions; Node = mover->take(); - Invalid = mover->Invalid; return *this; } - /// Move assignment from an owning ptr - ASTOwningResult & operator =(moving::ASTPtrMover mover); - /// Assignment from a raw pointer. Takes ownership - beware! - ASTOwningResult & operator =(void *raw) + ASTOwningPtr & operator =(void *raw) { - assert((!raw || Actions) && - "Cannot have raw assignment when there's no Action"); + assert((Actions || !raw) && "Cannot assign non-null raw without Action"); Node = raw; - Invalid = false; - return *this; - } - - /// Assignment from an ActionResult. Takes ownership - beware! - ASTOwningResult & operator =(const DumbResult &res) { - assert((!res.Val || Actions) && - "Cannot assign from ActionResult when there's no Action"); - Node = res.Val; - Invalid = res.isInvalid; return *this; } /// Access to the raw pointer. void * get() const { return Node; } - bool isInvalid() const { return Invalid; } - - /// Does this point to a usable AST node? To be usable, the node must be - /// valid and non-null. - bool isUsable() const { return !Invalid && Node; } - - /// Take outside ownership of the raw pointer. + /// Release the raw pointer. void * take() { - if (Invalid) - return 0; void *tmp = Node; Node = 0; return tmp; @@ -220,85 +277,109 @@ namespace clang return take(); } - /// Pass ownership to a classical ActionResult. - DumbResult result() { - if (Invalid) - return true; - return Node; - } + /// Get the Action associated with the node. + ActionBase* getActions() const { return Actions; } /// Move hook - operator moving::ASTResultMover() { - return moving::ASTResultMover(*this); + operator moving::ASTPtrMover() { + return moving::ASTPtrMover(*this); } }; template - class ASTOwningPtr + class ASTOwningResult { - ActionBase *Actions; - void *Node; - - friend class moving::ASTPtrMover; - friend class ASTOwningResult; + ASTOwningPtr Ptr; + bool Invalid; - ASTOwningPtr(ASTOwningPtr&); // DO NOT IMPLEMENT - ASTOwningPtr& operator =(ASTOwningPtr&); // DO NOT IMPLEMENT + friend class moving::ASTResultMover; - void destroy() { - if (Node) { - assert(Actions && "Owning pointer without Action owns node."); - (Actions->*Destroyer)(Node); - } - } + ASTOwningResult(ASTOwningResult&); // DO NOT IMPLEMENT + ASTOwningResult& operator =(ASTOwningResult&); // DO NOT IMPLEMENT public: - explicit ASTOwningPtr(ActionBase &actions) - : Actions(&actions), Node(0) {} - ASTOwningPtr(ActionBase &actions, void *node) - : Actions(&actions), Node(node) {} - /// Move from another owning pointer - ASTOwningPtr(moving::ASTPtrMover mover) - : Actions(mover->Actions), Node(mover->take()) {} - /// Move from an owning result - ASTOwningPtr(moving::ASTResultMover mover); + typedef ActionBase::ActionResult::UID> DumbResult; - /// Move assignment from another owning pointer - ASTOwningPtr & operator =(moving::ASTPtrMover mover) { - Actions = mover->Actions; - Node = mover->take(); + explicit ASTOwningResult(ActionBase &actions, bool invalid = false) + : Ptr(actions, 0), Invalid(invalid) {} + ASTOwningResult(ActionBase &actions, void *node) + : Ptr(actions, node), Invalid(false) {} + ASTOwningResult(ActionBase &actions, const DumbResult &res) + : Ptr(actions, res.Val), Invalid(res.isInvalid) {} + /// Move from another owning result + ASTOwningResult(moving::ASTResultMover mover) + : Ptr(moving::ASTPtrMover(mover->Ptr)), + Invalid(mover->Invalid) {} + /// Move from an owning pointer + ASTOwningResult(moving::ASTPtrMover mover) + : Ptr(mover), Invalid(false) {} + + /// Move assignment from another owning result + ASTOwningResult & operator =(moving::ASTResultMover mover) { + Ptr = move(mover->Ptr); + Invalid = mover->Invalid; return *this; } - /// Move assignment from an owning result - ASTOwningPtr & operator =(moving::ASTResultMover mover); + /// Move assignment from an owning ptr + ASTOwningResult & operator =(moving::ASTPtrMover mover) { + Ptr = mover; + Invalid = false; + return *this; + } /// Assignment from a raw pointer. Takes ownership - beware! - ASTOwningPtr & operator =(void *raw) + ASTOwningResult & operator =(void *raw) { - assert((Actions || !raw) && "Cannot assign non-null raw without Action"); - Node = raw; + Ptr = raw; + Invalid = false; + return *this; + } + + /// Assignment from an ActionResult. Takes ownership - beware! + ASTOwningResult & operator =(const DumbResult &res) { + Ptr = res.Val; + Invalid = res.isInvalid; return *this; } /// Access to the raw pointer. - void * get() const { return Node; } + void * get() const { return Ptr.get(); } - /// Release the raw pointer. + bool isInvalid() const { return Invalid; } + + /// Does this point to a usable AST node? To be usable, the node must be + /// valid and non-null. + bool isUsable() const { return !Invalid && get(); } + + /// Take outside ownership of the raw pointer. void * take() { - void *tmp = Node; - Node = 0; - return tmp; + if (Invalid) + return 0; + return Ptr.take(); } /// Alias for interface familiarity with unique_ptr. - void * release() { - return take(); + void * release() { return take(); } + + /// Pass ownership to a classical ActionResult. + DumbResult result() { + if (Invalid) + return true; + return Ptr.take(); } + /// Get the Action associated with the node. + ActionBase* getActions() const { return Ptr.getActions(); } + /// Move hook - operator moving::ASTPtrMover() { - return moving::ASTPtrMover(*this); + operator moving::ASTResultMover() { + return moving::ASTResultMover(*this); + } + + /// Special function for moving to an OwningPtr. + moving::ASTPtrMover ptr_move() { + return moving::ASTPtrMover(Ptr); } }; @@ -348,6 +429,13 @@ namespace clang /// Access to the count. unsigned size() const { return Count; } + void ** release() { + void **tmp = Nodes; + Nodes = 0; + Count = 0; + return tmp; + } + /// Move hook operator moving::ASTMultiMover() { return moving::ASTMultiMover(*this); @@ -362,33 +450,6 @@ namespace clang Moved.Count = 0; } - template inline - ASTOwningResult::ASTOwningResult( - moving::ASTPtrMover mover) - : Actions(mover->Actions), Node(mover->take()), Invalid(false) {} - - template inline - ASTOwningResult & - ASTOwningResult::operator =(moving::ASTPtrMover mover) { - Actions = mover->Actions; - Node = mover->take(); - Invalid = false; - return *this; - } - - template inline - ASTOwningPtr::ASTOwningPtr(moving::ASTResultMover mover) - : Actions(mover->Actions), Node(mover->take()) { - } - - template inline - ASTOwningPtr & - ASTOwningPtr::operator =(moving::ASTResultMover mover) { - Actions = mover->Actions; - Node = mover->take(); - return *this; - } - // Move overloads. template inline @@ -406,17 +467,18 @@ namespace clang return ASTMultiPtr(moving::ASTMultiMover(ptr)); } - // A shortcoming of the move emulation is that Ptr = move(Result) doesn't work + // These are necessary because of ambiguity problems. template inline - ASTOwningResult move_convert(ASTOwningPtr &ptr) { - return ASTOwningResult(moving::ASTPtrMover(ptr)); + ASTOwningPtr move_convert(ASTOwningResult &ptr) { + return ASTOwningPtr(ptr.ptr_move()); } template inline - ASTOwningPtr move_convert(ASTOwningResult &ptr) { - return ASTOwningPtr(moving::ASTResultMover(ptr)); + ASTOwningResult move_convert(ASTOwningPtr &ptr) { + return ASTOwningResult(moving::ASTPtrMover(ptr)); } + } #endif -- GitLab