[cfe-dev] Ownership of Stmts and Exprs, and destroying them

Howard Hinnant hhinnant at apple.com
Tue Nov 25 13:00:44 PST 2008


On Nov 25, 2008, at 1:47 PM, Chris Lattner wrote:

> On Nov 24, 2008, at 7:36 PM, Doug Gregor wrote:
>>>> Similarly for ExprResult and
>>>> friends. That way, the type system enforces our ownership model
>>>> (transfer into Sema for the parameters of ActOn*, transfer out of
>>>> Sema
>>>> on return), and we're safe from early exits.
>>>
>>> ExprResult is just a pair of success + pointer.  The actions in
>>> Sema are
>>> only called in the success case.
>>
>> Of course. But ExprResult also (implicitly!) carries with it the
>> ownership of the pointer, and that's not at all clear now.
>
> Ok, I agree with you.  However, making the contract more clear seems
> conceptually separate from making sema or the parser not leak.  Are
> you saying that both problems could be solved in the same way?

I don't want to speak for Doug, but here is some working C++0X demo  
code which I believe demonstrates.  Parts of it were borrowed from  
Sebastian's patch:

#include <memory>
#include <cstdio>

class Action
{
public:
     void DeleteStmt(void*) {std::printf("DeleteStmt\n");}
     void DeleteExpr(void*) {std::printf("DeleteExpr\n");}
};

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);
   }
};

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

ExprGuard
ExprGuardSource(Action& Actions)
{
     return ExprGuard((void*)1, ExprGuard::deleter_type(Actions));
}

void
ExprGuardSink(ExprGuard p1)
{
     std::printf("ExprGuardSink: begin\n");
     {
     ExprGuard p2 = std::move(p1);
     }  // DeleteExpr called here
     std::printf("ExprGuardSink: end\n");
}

int main()
{
     std::printf("main: begin\n");
     void* Body = 0;
     Action Actions;
     StmtGuard BodyGuard1(Body, StmtGuard::deleter_type(Actions));
     ExprGuard BodyGuard2 = ExprGuardSource(Actions);
     ExprGuardSink(std::move(BodyGuard2));
     std::printf("main: end\n");
}

Output:

main: begin
ExprGuardSink: begin
DeleteExpr
ExprGuardSink: end
main: end

Explanation:

DeleteStmt never fires because the void* in BodyGuard1 is null.  The  
void* briefly owned by BodyGuard2 in main() begins life in  
ExprGuardSource, gets passed up to main (without a release(), or  
take() as it is currently spelled), and then passed back down to  
ExprGuardSink which eventually runs DeleteExpr on it.  Both StmtGuard  
and ExprGuard have member get(), with the same semantics as ASTGuard,  
and member release() with the same semantics as ASTGuard::take().   
They lack the reset() templated on N (since unique_ptr is ignorant of  
Action), but have the other reset().

Problems:

The above code was compiled with g++-4.3 -std=c++0x -nostdinc++ and  
the emulation at:

    http://home.twcny.rr.com/hinnant/cpp_extensions/unique_ptr_03.html

will not work in the above example (it depended on a gcc bug that has  
since been fixed).  I believe Dave Abrahams has a move emulation  
package at boost that could possibly be adapted, but that is more work  
and complexity which may or may not be warranted.  I believe it  
probably works similarly to today's auto_ptr, but perhaps disables  
moving from lvalues; <shrug> I haven't looked at it in years.

Also one needs to be careful with the Action& lifetime living in the  
deleter that is getting passed around inside the unique_ptr.

The main difference between unique_ptr<void,  
ASTDeleter<&Action::DeleteStmt> > and ASTGuard is that the former is  
moveable and thus both documents and enforces the pointer transfer of  
ownership from scope to scope.  Otherwise they do the same job.

-Howard




More information about the cfe-dev mailing list