[cfe-dev] Parser Stmt/Expr Owning Pointer

Howard Hinnant hhinnant at apple.com
Mon Dec 8 18:57:33 PST 2008


On Dec 8, 2008, at 9:04 PM, Chris Lattner wrote:

>> I personally don't find move() strange at all, but then I've been
>> preoccupied with move semantics and their emulation since I learned
>> of the concept two years ago. (Note: with proper C++0x moving,
>> there'll be no need to call move() anymore. However, due to the
>> reference binding rules of C++03, the only way to achieve this is to
>> allow the highly unsafe practice of moving from const references.)
>
> Yep yep, rvalue refs are very nice.  Unfortunately we probably can't
> actually *use* them in llvm/clang until they are widely deployed in
> vendor compilers.  :(  It sucks to have to write portable code.
>
> What do other people think about this name?

It shouldn't be surprising that I prefer move().  In fact I'd prefer a  
namespace scope move, but I have no real problem with the member  
move.  The name take() reminds me more of release(), and on inspection  
of OwningPtr.h appears to have the same semantics.  release() says  
that you're giving up ownership, and somebody better take over.   
move() says: treat this object as a temporary and do with it what you  
want.  If you do nothing, a move() still shouldn't leak:

{
unique_ptr<int> p(new int);
move(p);
}  // the memory is not leaked

{
unique_ptr<int> p(new int);
p.release();
}  // the memory is leaked

release/take returns a raw pointer.  Somebody has to catch it.  The C+ 
+0X move(unique_ptr<T>) returns a unique_ptr<T>&&.  The C++03  
emulation I'm working on returns a unique_ptr<T>.  The resource is  
never exposed.  You can not pass it to somebody and have them forget  
to own it (like you can a raw pointer).

Sebastian's ASTOwner::move() returns a ASTMove.  I'm not yet positive  
what it will do with:

{
ASTOwner<&Action::DeleteStmt> p(actions);
p.move();
}  // leaked?

And has a disadvantage that if passed to a templated function, the  
wrong type gets deduced.

template <class T> void foo(T);

foo(p.move());  // T deduced as ASTMove instead of as ASTOwner

Fwiw I'm pretty close to a C++03 unique_ptr that is passing all my  
tests so far.  It needs a <type_traits> (which I also have).  It  
doesn't have the "moves from const" bug that my previous emulation  
attempt had.

I just ran it through this demo (using g++-4.0 and g++-4.2):

// Move mini-tutorial with unique_ptr.
//
// If the unique_ptr has a name and you want
//    to transfer ownership to a new scope,
//    then you have to use std::move().
// If the unique_ptr doesn't have a name
//    (because it is a temporary), then
//    you don't need to wrap it in a
//    std::move() to move it.  It will move
//    anyway.
// If you accidently forget a move where it is
//    needed (such as from an lvalue), then your
//    code will not compile.

#include <memory>
#include <cstdio>

class Action
{
public:
     void DeleteStmt(void* p) {std::printf("DeleteStmt operating on %p 
\n", p);}
     void DeleteExpr(void* p) {std::printf("DeleteExpr operating on %p 
\n", p);}
};

template <void (Action::*Destroyer)(void*)>
class ASTDeleter {
   Action &Actions;

   // Reference member prevents copy assignment.

public:
   explicit ASTDeleter(Action &actions) : Actions(actions) {}

   void operator()(void* Node) {
       (Actions.*Destroyer)(Node);  // no null check needed
   }
};

typedef std::unique_ptr<void, ASTDeleter<&Action::DeleteStmt> >  
StmtGuard;
typedef std::unique_ptr<void, ASTDeleter<&Action::DeleteExpr> >  
ExprGuard;

ExprGuard
ExprGuardSource(Action& Actions)
{
     std::printf("ExprGuardSource: begin\n");
     // Exactly one "new"
     ExprGuard ex((void*)1, ExprGuard::deleter_type(Actions));
     std::printf("ExprGuardSource: end\n");
     return move(ex);  // explicit move needed for C++03 emulation
}

void
ExprGuardSink(ExprGuard p1)
{
     std::printf("ExprGuardSink: begin\n");
     {
     ExprGuard p2 = std::move(p1);  // won't compile without move
     }  // DeleteExpr called here, exactly once
     std::printf("ExprGuardSink: end\n");
}

int main()
{
     std::printf("main: begin\n");
     void* Body = 0;
     Action Actions;
     std::printf("main: constructing BodyGuard1\n");
     {
     StmtGuard BodyGuard1(Body, StmtGuard::deleter_type(Actions));
     }
     std::printf("main: BodyGuard1 is destructed\n");
     std::printf("main: calling ExprGuardSource\n");
     {
     std::printf("main: constructing BodyGuard2\n");
     ExprGuard BodyGuard2 = ExprGuardSource(Actions);
     std::printf("main: calling ExprGuardSink\n");
     ExprGuardSink(std::move(BodyGuard2));  // won't compile without  
move
     }
     std::printf("main: BodyGuard2 is destructed\n");
     std::printf("main: end\n");
}

and the output is:

main: begin
main: constructing BodyGuard1
main: BodyGuard1 is destructed
main: calling ExprGuardSource
main: constructing BodyGuard2
ExprGuardSource: begin
ExprGuardSource: end
main: calling ExprGuardSink
ExprGuardSink: begin
DeleteExpr operating on 0x1
ExprGuardSink: end
main: BodyGuard2 is destructed
main: end

Like Sebastian's ASTOwner, it needs an explicit move on return  
statements, unless you are returning an rvalue.  This move would best  
be removed when C++03 compilers no longer need to be supported.   
However in C++0X the extraneous move will inhibit RVO, but otherwise  
will not impact the logic.

-Howard




More information about the cfe-dev mailing list