[cfe-dev] Parser Stmt/Expr Owning Pointer

Howard Hinnant hhinnant at apple.com
Tue Dec 9 07:32:44 PST 2008


On Dec 9, 2008, at 5:05 AM, Sebastian Redl wrote:

> I'm looking forward to seeing your tricks to allow a namespace scope  
> move bind to temporaries but not const lvalues. I don't know how to  
> do it, so my move() is a member.

The design is pretty much taken from auto_ptr.  It is a painful but  
effective means of installing move semantics into a class.

>> And has a disadvantage that if passed to a templated function, the   
>> wrong type gets deduced.
>>
> True. I find the likelihood of this in the Clang source low.

Agreed.

>> 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'd love to see it.

I'll try to get it in soon.  I'm not yet positive what "in" means.   
Does the clang project want unique_ptr?  How should it handle pre-std  
library components (what namespace)?  Does clang want it prepped for  
rvalue-ref support via #ifdef's, or just have the emulation?

>> 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
>>   }
>> };
>>
> Is there a specific reason the constructor can't be implicit? Then  
> you could leave out the long typedef here:
>>     ExprGuard ex((void*)1, ExprGuard::deleter_type(Actions));
>>
> and just write ExprGuard ex(ptr, Actions).

No reason at all.  I was just "winging" an ASTDeleter by imitating  
your code without really understanding it at all.

> Also, can I trick your unique_ptr to give up its raw pointer after  
> it has been moved from? For convenience in the transition phase,  
> that would be invaluable. In my current implementation you can do:
>
> void takesOwnershipButHasNotBeenConvertedToBeSmart(ExprTy*);
>
> ExprOwner ptr(getExprFromSomewhere());
> takesOwnershipButHasNotBeenConvertedToBeSmart(ptr.move());
>
> The important part is that it's always move(), even after converting  
> the function to take an ExprOwner.

I'm not positive I understand your question 100%, but I'll try a  
shotgun answer.  If I don't hit the target, just ask again. :-)

unique_ptr is basically designed to be API compatible with auto_ptr as  
much as possible.  Like auto_ptr it has members:

T* get() const;  // return pointer without giving up ownership
T* release();    // return pointer and give up ownership.   
postcondition is get() == 0.

After a move, the source.get() == 0.  You can also source.release()  
and it will also return 0.

If I'm reading your code example correctly, I think this is as simple  
as:

    ExprOwner ptr(getExprFromSomewhere());
    takesOwnershipButHasNotBeenConvertedToBeSmart(ptr.release());

However, after takesOwnershipButHasNotBeenConvertedToBeSmart is  
converted to take a unique_ptr:

     void takesOwnership(unique_ptr<T>);

then the client code could change in one of several ways, but would  
have to change (else compile-time error):

    takesOwnership(unique_ptr<T>(ptr.release()));

or more simply:

    takesOwnership(move(ptr));

or if logic allows, just:

    takesOwnership(getExprFromSomewhere());  // assumes  
getExprFromSomewhere() returns a unique_ptr

So the syntax is different for releasing ownership to a raw pointer,  
and transferring ownership to another unique_ptr.  And the compiler  
will tell you when and where you need to change when  
takesOwnershipButHasNotBeenConvertedToBeSmart gets updated.  You won't  
create run time errors.  I believe having different syntax is safer.   
release() is by nature more dangerous than move().  If you're using  
release(), there is a potential for leaking - you are transferring  
ownership to a raw pointer.  If you're using move(), there is no  
potential for leaking.  After the transition period, release() should  
be used relatively rarely.  It will be helpful to be able to search  
for these places and not get drowned out by all the "moves".

If it helps, here is the current C++0X synopsis:

template <class T, class D = default_delete<T>>
class unique_ptr
{
public:
     typedef implementation-defined pointer;
     typedef T element_type;
     typedef D deleter_type;

     // constructors
     unique_ptr();
     explicit unique_ptr(pointer p);
     unique_ptr(pointer p, implementation-defined d);
     unique_ptr(pointer p, implementation-defined d);
     unique_ptr(uniquePtr&& u);
     unique_ptr(nullptr_t);
     template <class U, class E> unique_ptr(unique_ptr<U, E>&& u);

     // destructor
     ∼unique_ptr();

     // assignment
     unique_ptr& operator=(unique_ptr&& u);
     template <class U, class E> unique_ptr& operator=(unique_ptr<U,  
E>&& u);
     unique_ptr& operator=(unspecified-pointer-type);

     // observers
     typename add_lvalue_reference<T>::type operator*() const;
     pointer operator->() const;
     pointer get() const;
     deleter_type& get_deleter();
     const deleter_type& get_deleter() const;
     explicit operator bool() const;

     // modifiers
     pointer release();
     void reset(pointer p = pointer());
     void swap(unique_ptr&& u);

     // disable copy from lvalue
     unique_ptr(const unique_ptr&) = delete;
     template <class U, class E> unique_ptr(const unique_ptr<U, E>&) =  
delete;
     unique_ptr& operator=(const unique_ptr&) = delete;
     template <class U, class E> unique_ptr& operator=(const  
unique_ptr<U, E>&) = delete;
};

Ref: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/ 
n2798.pdf  20.8.12  [unique.ptr].

The emulation I have neglects construction from nullptr_t, but does  
support construction and assignment from 0.  The member swap is likely  
soon to be changed to take unique_ptr& instead of unique_ptr&& (a  
minor change).  It supports custom pointer types, T == void,  
incomplete T except for when the deleter is called, stateful deleters,  
and deleters which are lvalue reference types.  The ASTDeleter deleter  
example I gave is something I would call a stateful deleter.  T ==  
void is good for the kinds of things we want to do in clang:  keep  
specific types out of the interface.  Use of incomplete (but non-void)  
T is also helpful in this department (pimpl pattern).

-Howard





More information about the cfe-dev mailing list