[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