Tuesday, September 23, 2008

C++ Corner - Smart pointer #1: scoped pointer

Now this post is gonna be pretty dry and boring for some of you and exciting for the rest of you. But, one thing, for sure, it's gonna go a little technical and you might need to google some stuffs along the way.

Before we start, let's read something more light-hearted first. Please read this. No, seriously read it. It'll lighten up your mind a little and make you happier.

It's a funny manly bike ads from craigslist recently (archived to my web domain). (If it wasn't funny, well, you can bash... nah, you can't do anything to me, too bad!)

Okay, let's get movin'. This is my first technical write up aimed at more general audience, do provide some feedbacks when you think of any! (:



Umm. Pointers? Smart? Woot? Worry not, by the end of this post, you'd know about pointers and smart pointers well enough to feel confident to code with them (and of course finding out that you're dead wrong when faced with a wall of g++ compile errors and segfault-ing C++ executables . . kidding! I know you guys are smart enough to handle C++).

C and C++ are known for their notoriously memory-leak prone memory management. This is because you as the developer is in charge of doing the memory management. That is not as easy as you might think as we will see later. There is an issue of when to deallocate allocated memory, what to do to make sure that the deletion is executed (what if there is an exception thrown earlier in the function?). Now the smart pointers attempt to make memory management a lot more manageable (pun not intended). By the end of this "article" (yes, it's gonna be long!), you should know a lot more about C++ pointers and hopefully you will be more confident diving into C++. Even if you don't know C++, it's a good idea to know about these smarty pants.

Stack vs. free store

We start with the very basic memory lesson: stack vs. free store. Memory allocation in C++ (and as you'll see later, in Java and in simPL/rePL in CS3000 something Programming Language class) are divided into two types, automatic allocations and manual allocations. Let's check out the easy one first: automatic allocations.
void Foo() {
int number = 1; // auto variable
for (int i = 0; i < 10; ++i) { // i is also auto variable
string str("Hello"); // this string object is auto!
}
number = i; // wait a minute, i no longer exists!
cout << str; // hey, str no longer exists too!
}

Sounds pretty normal, right? As the name suggest, you needn't do anything, everything is managed automatically by the compiler. These variables will be allocated in the program stack. There is a problem though. What about if you create a new class in the scope of Foo function and pass str into the new class. You could pass str by copying it and all is fine. But copying is slow, so you would most likely want to pass anything that is not a simple primitives (primitives == int, double, float; non-primitives == object) by reference. Java defaults to exactly that. Primitives are passed by values and objects are passed by reference. But if you do pass str by reference:
ClassA* CreateA() {
string str("Test"); // auto variable!
return new ClassA(&str); // pass the memory addr of str.
}

class ClassA {
public:
ClassA(string* str) : str_(str) {} // constructor, assign str to str_.
void PrintString() { cout << str_; }
private:
string* str_;
};

int main() {
ClassA* a = CreateA();
a->PrintString(); // fails! segmentation fault!
}

Why does it fail (segfault means that you access a memory address that doesn't belong to you)? Because by the time PrintString is called, the string pointed by str_ (which is str) has gone out of scope and is no longer available in the program stack! It has been destroyed! This is true for both primitives and objects! (Copying the object works though and you can copy primitives, but, copying object would be too expensive almost all the time.)

So auto variable doesn't work. This is where free store comes to play. Memory allocated in free store will remain available for the duration of the program (or until you explicitly deallocate it). Anything that is created with new (or malloc in C-style code) are allocated in the free store. Let's rewrite Foo now:
ClassA* Foo() {
string* str = new string("test"); // string* is now a pointer!
return new ClassA(str);
}

There you go! Now the program works just fine! A pointer is something that contains a memory address of the object allocated by new (or have a memory address of an object assigned to it, e.g. string* str = new string("test"); string* str2 = str;). Thus you can pass the pointers around and everyone will be able to access the same object (like in Java). Happy!

Um, no, it's not a happy ending (yet). What is the problem with this? The problem is the phrase "until you explicitly deallocate" the allocated memory. This is where memory leaks come from. To deallocate an object allocated by new, you call delete on the object (to be exact, on any of the pointer that points to the object). e.g. string* a = ...; delete a; will delete whatever object a pointer points to. Now let's do something simple:

string* str = new string("test");
// do something . . .
str = new string("test2");

Wait a minute. Simple my ar*e! Do you see anything wrong with this code? Assuming that str was the only guy pointing to "test" when you reassigned str to the new string "test2", you just lost a pointer to the string object "test"! Congrats! You just had a memory leak! Imagine similar code in a loop:
string* str;
for (int i = 0; i < 10000; ++i) {
str = new string("hello");
}

Wow! You just lost 9999 instances of the string "hello" without knowing it!! (Dumb example I know, but similar, more complicated codes have caused nightmare elsewhere.)

Important note: auto variable will always allocate the object and call the constructor of the object. You can think of auto variable as calling new and assigning the object to the auto variable. The difference is that the allocated object is allocated in the stack space (thus, you can call things like: vector<int> a(); vector<int> b(a); where b copies vector a by calling vector<int> copy constructor). There are more complicated thing with copy assignment, but let's leave that aside for now. (Take a look at this article if you want to know more.)

Note: This stack vs. free storage works exactly the same in Java. Everything allocated with new is allocated in free storage. Everything that is not (int, double, float, char, etc.) are allocated in stack (and they are passed by value, i.e. the value is copied instead of passed by reference). The difference is that Java Virtual Machine (JVM) has a garbage collector (GC) that checks the free storage periodically to delete objects that are no longer accessible from the application. (Note: GC is slow(er) than manual memory management! If you don't optimize your JVM's GC parameters correctly, a memory-hungry programs may freeze for several seconds during garbage collection phase).

The first smart pointer: scoped_ptr

The first of the smart pointers that will help you manage pointers is the scoped pointer.

When should you be using it? When the ownership of the object is clear! That means, you can say for sure that object A should be the owner of the object obj, and object A should outlive every functions and objects that need to use obj. Otherwise, of course, segmentation fault may occur.

How does it work?
We are going to look at how Boost (a C++ library) implements scoped pointer. Let's take a look (I have taken a liberty of stealing Boost's interface and implement it myself (and cutting corners):

// Note the use of template here, similar to Java generics.
template<typedef T> class scoped_ptr : noncopyable {
public:
// Constructor assign p to p_ private variable. Now we own the object.
explicit scoped_ptr(T* p = NULL) : p_(p) {}

// Destructor will delete the object.
~scoped_ptr() {
// Delete the object being pointed by this scoped_ptr.
delete p_; // okay to delete NULL
}

void reset(T* p = NULL) {
// Check that the passed pointer is not the same!
if (p_ != p) {
// Delete the pointer we are holding before assigning the new one.
delete p_;
p_ = p;
}
}

T& operator*() const { return *p_; }
T* operator->() const { return p_; }
T* get() const { return p_; }

private:
T* p_;
};

Now we can use scoped_ptr as an auto variable (do not use it as a pointer). Whenever the scoped_ptr goes out of scope, the destructor (~scoped_ptr) will always be called. Therefore the object pointed by the scoped_ptr will be deleted automatically!

scoped_ptr owns the pointer. Thus it is free to delete the pointer when it is being destroyed (~scoped_ptr destructor) or when it is being reset (reset method). We also provide operator* and operator-> so that we can use scoped pointer as if it's a normal pointer (this is something you can't do in Java: operator overloading). If we need to get the pointer itself, e.g. to pass to another method that expect a pointer, call get method.

Also note that scoped_ptr is non-copyable. You probably can imagine what will happen if two scoped pointer holds the same object. The object will be deleted twice! Segfault! (There is a method that you can use to swap the contents of two scoped_ptr, aptly named swap.)

How to use scoped pointer
Consider two things before using scoped pointers. One: do I know who owns the object clearly (if not, consider using shared pointer, coming up in another post)? Two: can I use auto-variable instead? If the answer to both questions are 'yes' then use scoped pointer. Place the scoped pointer with the owner.

Wait a minute. If I can use auto-variable, why would I want to use scoped pointers? Answer: to avoid copying! Remember auto-variable will call the constructor of the object. e.g. ClassA a;, a seemingly side-effect-less thing actually calls the default constructor of ClassA, which might be doing a lot of work! Let's illustrate with examples shall we?

// Usage illustration for scoped_ptr.
class ThunderBirdHawk {
private:
// Allocating and calling constructor of AddressBook may be
// expensive. Additionally a may not be used at all, so we
// want to lazily instantiate address_book_.
scoped_ptr<AddressBook> address_book_;

MailSender sender_;

public:
// Constructor will only call scoped_ptr constructor, which is cheap.
// Note constructor of MailSender is called whenever the ThunderHawk
// is created, here constructor that accepts 1 int argument (the port
// number) is called.
ThunderHawk() : address_book_(NULL), sender_(25) {}

// Destructor
~ThunderHawk() { /* do nothing for now. */ };

// . . . Many methods here . . .

// This method is one of the two methods that use a_.
void SendEmailUsingAlias(
const string& msg, const string& recipient) {
// Now we need to find the e-mail address from address book.
if (address_book_.get() != NULL) {
// Try loading address book (by calling LoadAddressBook).
a_.reset(LoadAddressBook());

// If could not load address book, load default address book.
// Another advantage of pointer is you can use inheritance!
if (address_book_.get() != NULL)
address_book_.reset(new DefaultAddressBook());
}

// . . . Now we can use address_book_ like any normal pointer.
address_book_->Validate();

// We can also pass address_book_ to to the sender_ object,
// and not worrying of address_book_ going out of scope.
// Note that this call is non-blocking/asynchronous.
sender_.AsynchronousSend(msg, address_book_.get());
}

AddressBook* LoadAddressBook() {
// Note here, while LoadAddressBook creates the object, it does
// not own the object; instead passing ownership to the caller.
try {
return new AddressBook();
} catch (...) { return NULL; }
}
}

What is the primary advantage of scoped pointer? The pointer contained in the scoped pointer is guaranteed to be deleted when the scoped pointer goes out of scope, even if the object destructor somehow throws an exception. scoped_ptr's destructor should never throw, but ThunderHawk's destructor might before you got the chance to delete a_. Without scoped pointer, there is no guarantee that your delete address_book_ below is executed:
AddressBook* address_book_;
~ThunderHawk() {
// do some cleanup here, may throw exception.
delete address_book_; // what if exception was thrown earlier!?
}

Furthermore, you can free your mind tracking more important stuffs than making sure that the scoped pointer is destroyed!

So how was that? Does scoped pointer goodness makes you feel all warm and fuzzy inside? It did the first time I saw it (and no! It's not love, though you might mistake it for one). It has a very smart and twisted implementation. If you want to use scoped pointer, consider using Boost C++ library instead of writing one yourself (which is prone to errors). Alternatively, use auto_ptr, which is a more problematic version of Boost's scoped_ptr, but included directly in C++ standard header <memory>.

scoped_ptr: use it as often as you can!

Coming up sometime later this week will be shared_ptr, a more powerful, reference-counted smart pointer that enables shared ownership of an object (it should be used more rarely though, using shared pointers often could mean that your program design is bad).

- Chris

2 comments:

Anonymous said...

great article :-)
Please keep posting another smart pointer article.

chris said...

Thanks! I just noticed the comment (the peril of being a guest blogger). You can see my other post on smart pointer in my blog here.