[cfe-commits] r60900 - in /cfe/trunk: include/clang/Parse/Ownership.h

Howard Hinnant hhinnant at apple.com
Sat Dec 13 13:21:21 PST 2008


In an effort to "de-mystify" the subject around this "smart pointer"  
application I would like to take include/llvm/ADT/OwningPtr.h as  
example code.  I've enclosed a modified OwningPtr.h just as an  
example, not as a proposed change.  I encourage everyone to do a diff  
between the enclosure and the current OwningPtr.h (the diff is small &  
boilerplate).

Consider this very simplistic sample code which is written to the  
current OwningPtr.h interface:

#include <iostream>
#include "OwningPtr.h"

int* source(int i)
{
     llvm::OwningPtr<int> p(new int(i));
     std::cout << "source: " << *p << '\n';
     return p.take();
}

void sink1(int* i)
{
     llvm::OwningPtr<int> p(i);
     std::cout << "sink1: " << *p << '\n';
}

void sink2(int* i)
{
     std::cout << "sink2: " << *i << '\n';
}

int main()
{
     sink1(source(1));
     sink2(source(2));
}

There is a "source" function which creates an OwningPtr, but does not  
return an OwningPtr because OwningPtr isn't copy constructible.  It  
instead returns a pointer to the data (just an int here, likely an  
Expr in clang).

There are two clients: sink1 and sink2.  The clients have similar  
signatures, but different semantics.  sink1 takes ownership of the  
pointer.  sink2 does not.  sink2 may be this way by design, or this  
could be coding error.

The example above compiles, and prints out:

source: 1
sink1: 1
source: 2
sink2: 2

Because I ran it with a memory-leak checker, it also printed out:

Leaking memory (8 bytes) at 0xb010
Total memory leaked is 8 bytes

Now consider substituting in the modified OwningPtr.h:

source: 1
sink1: 1
source: 2
sink2: 2
Leaking memory (8 bytes) at 0xb010
Total memory leaked is 8 bytes

well, same problem.

Note:  This modification won't alter existing behavior.  However it  
enables precautions to be taken to make the code safer.  The first  
precaution to be made is to alter "source" to return an OwningPtr.   
Before we couldn't do this.  Now we can:

llvm::OwningPtr<int> source(int i)
{
     llvm::OwningPtr<int> p(new int(i));
     std::cout << "source: " << *p << '\n';
     return move(p);
}

Compile time errors:

test.cpp: In function ‘int main()’:
test.cpp:52: error: cannot convert ‘llvm::OwningPtr<int>’ to ‘int*’  
for argument ‘1’ to ‘void sink1(int*)’
test.cpp:53: error: cannot convert ‘llvm::OwningPtr<int>’ to ‘int*’  
for argument ‘1’ to ‘void sink2(int*)’

The compiler is now using the type system to alert the programmer to  
possible memory leaks.  The first error is pointing to:

     sink1(source(1));

Ok, OwningPtr (correctly) will not implicitly convert to a raw  
pointer.  We note that sink1 was designed to accept ownership of the  
pointer so we modify it like so:

void sink1(llvm::OwningPtr<int> p)
{
     std::cout << "sink1: " << *p << '\n';
}

Note that we can now use OwningPtr to pass by value to functions.   
This is a pattern, like auto_ptr, that indicates to the client that  
sink1 is accepting ownership of the referenced resource.

The second error refers to:

     sink2(source(2));

Same problem.  We must now decide if sink2 is correct in not claiming  
ownership of the resource, or if it is simply buggy.  If it is a bug,  
we fix it the same way we fixed sink1.  So let's assume that sink2 is  
correct as is.  We have to change our call site then:

     sink2(source(2).get());

Now the sample compiles and prints out:

source: 1
sink1: 1
source: 2
sink2: 2

There are no memory leaks.  By eliminating the use of "take()", we  
have greatly reduced our exposure to leaks because unowned raw  
pointers are never passed around.  Yet we can still pass in (owned)  
raw pointers (as in the sink2 example) when the API calls for it.

Portability:  All compilers that enforce the rule that temporaries can  
not bind to non-const references will compile this code.  And there is  
a workaround for VC2005 (/Za off) which allows this example to compile  
and behave identically.

On VC2005 (/Za off), this modified OwningPtr will have approximately  
the behavior of std::auto_ptr.  Everywhere else it will refuse to move  
from lvalues with copy syntax:

    OwningPtr<int> p(new int(1));
    sink1(p);  // compile time error

Summary:  This change allows us to use the type system to help enforce  
resource ownership.  Exposure to unowned pointers (such as what take()  
returns) is minimized but still allowed.

For reference here is the complete altered example:

#include <iostream>
#include "OwningPtr.h"

llvm::OwningPtr<int> source(int i)
{
     llvm::OwningPtr<int> p(new int(i));
     std::cout << "source: " << *p << '\n';
     return move(p);
}

void sink1(llvm::OwningPtr<int> p)  // owns int*
{
     std::cout << "sink1: " << *p << '\n';
}

void sink2(int* i)  // doesn't own int*
{
     std::cout << "sink2: " << *i << '\n';
}

int main()
{
     sink1(source(1));
     sink2(source(2).get());
}

-Howard
-------------- next part --------------
A non-text attachment was scrubbed...
Name: OwningPtr.h
Type: application/octet-stream
Size: 3826 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20081213/761bf9ed/attachment.obj>
-------------- next part --------------



More information about the cfe-commits mailing list