[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