Making function declarations talk: auto_ptr and memory management in C++

Here’s just something I wrote some time ago and rotted on my harddisk. It might be helpful for people new to C++.

———————-
In all but the smallest projects, code is much more often read than (re)written. This means, that a large amount of the development time goes into reading code, which therefore should be made as easy as possible. It also means, that almost everything you can do, to make your code easier to be read should be done because the additional time you spent in writing the code will more than pay off in the future. We all know (and perhaps hate) some ways, to make code easier to read: use comments, use long variable names, give functions a clear name that corresponds to their purpose etc.

These rules can be applied in almost every programming language. But if you and your fellow developers really know the programming language, you can do more. You can express certain expectations and warranties by using features of the language and by this, express yourself more clearly (special bonus: you may even omit a few comments, because your code speaks for itself). Additionally, sometimes the compiler and the runtime environment help you to enforce certain requirements on how your code is to be used (see Design by Contract).

The places where well documented code is most important, are the interfaces of your classes. Everyone, who uses your class, will have a look at its interface and – if it’s well designed and documented – won’t need to look anywhere else. Especially not into your very private members and methods, where you hid all the quick’n dirty stuff you don’t want them to see. This is another special bonus: if your interface looks good, no one will care or notice how bad your implementation code looks (warning: this path leads to the dark side). And since you know the language features well, you can use them to document your interface. Let’s see how this is done.

It’s Monday morning and Pete walks down the hall, heading for his office. This week can only get better. Earlier that morning, his boss had loudly complained about his wife. His wife was totally into shoes. She has so many of them, that this weekend the unthinkable had happened. She bought a new pair on the weekend, only to find a very similar looking one already in her locker. „That’s enough“ Pete’s boss had said. „This won’t happen again!“
And so Pete and his fellow programmers got a new project to work on: A Shoe-Manager, to help his bosses wife keep track of her shoe-collection. It was Pete’s job, to design the database backend, that could save and load shoe-objects from the database. His first draft of the signature for the saveShoeToDatabase Method looked like this:

void saveShoeToDatabase(Shoe shoe);

Pete looks at it and suddenly it strikes him: „If I do it this way, the shoe-object given to my function will be copied. Since shoe-objects may have pictures or even 3D-Models (it’s a very fancy Shoe-Manager) this will kill performance. So he reconsiders:

void saveShoeToDatabase(Shoe* shoe);

Now only a pointer to a shoe-object will be copied. “Ha, this is fast”. And there Pete is. Right in the hottest pit of hell, dancing with the devil. The signature of this method raises a lot of questions to his colleague Jeff. He walks into Pete’s office and interrogates him: “Hey, the pointer points to some memory, which contains the shoe-object, that is to be saved. This memory is acquired by me. But what does your function do with it? Will it be deleted? That would mean, that the I have to copy his object, before I call your function. Will the pointer be stored for later use? That means that the I cannot delete my shoe-object until your database is done with it. But how will I know, when this time has come. And if I copy the object for you, how can I be certain that you don’t forget deleting it? And if I give you my original shoe-object, does your method change it? And what, if I hand you a NULL-Pointer? Can your weenie-Method handle that?”

Pete’s head is spinning. He decides to comment his method, to prevent any further yelling at him.

/* The memory will not be deleted or changed. The shoe-pointer may not be NULL. the given shoe will be saved to the database immediately. */
void saveShoeToDatabase(Shoe* shoe);

But Jeff is not satisfied. He doesn’t believe Pete, so every once in a while, he looks at the implementation of the saveShoeToDatabase-method and checks, if his precious shoes remain unchanged. When senior-programmer Jack hears of this, he walks to Pete and makes some suggestions to improve the method signature.
“Hey, if you do not change the memory, why do you not make the pointer constant, like this?”

void saveShoeToDatabase(const Shoe* shoe);

“Now the memory, to which shoe points cannot be changed or deleted. And, if you try to do it anyway, the compiler will complain. So we can omit the first sentence of your comment. Furthermore, you don’t want NULL-pointers. Then just change shoe into a reference. A reference works like a pointer, but it cannot be zero and once initialized it cannot point to anything else. This will allow you, to kill the second sentence in your comment and leave you with this.”

//The given shoe will be saved to the database immediately
void saveShoeToDatabase(const Shoe& shoe);

Pete is amazed and changes his method signature as Jack suggested. A week later, a problem arises. The Boss’ wife has so much shoes, that no ordinary pc is able to hold them all in memory at the same time. So the team decides that whenever a shoe is stored in the database it shall be wiped from memory.
Pete thinks about a redesign of his method: “Well, I cannot delete the memory, that is referenced by shoe, because shoe is const.” So he eliminates the const keyword and adds a comment, that informs the user of his class about his memory policies.

/*The given shoe will be saved to the database immediately. Afterwards, it will be deleted*/
void saveShoeToDatabase(Shoe& shoe);

Later that day, Jeff storms into his office: “Hey you jerk! My code doesn’t work anymore. But I changed nothing! So it’s your fault! Fix that, or I will fix you!”
Before Pete can say anything like “Did you read my comment?”, Jeff storms out again and slams the door. Obviously he hadn’t read the comment.

Seconds later the door opens again. It’s Jack asking “I saw Jeff. What’s the trouble?”
He takes a look at Pete’s function and says: “Oh, your function is a sink. You should use smart pointers!”
When he sees the puzzled look on Pete’s face, he decides to say a little more. “A sink is a function, that frees the memory that is passed to it. If a function calls delete on one or more of its parameters it is called a sink. The trouble with sinks is, that they look like every other function, but they behave differently. Once you have given something to a sink, it’s gone. That’s probably why Jeff’s code is not working anymore. He tries to do something with the shoe-object after the call to your function. And since you just removed the const qualifier from your shoe-reference, Jeff’s code still compiles. But it crashes at runtime, because it tries to operate on memory that has been given back to the operating system. What you need is a smart pointer, that makes it clear to the caller, that he looses the ownership of the object, when he passes it to your function. That is what auto_ptr was made for. It’s a smart pointer, that handles ownership. It makes sure, that only one auto_ptr can posess an object at a time. An auto_ptr is almost just like a normal pointer:

std::auto_ptr myShoe(new Shoe());

Here myShoe is an object of the type auto_ptr, which can be used as if we had written

Shoe* myShoe=new Shoe();

We can for example call methods:

myShoe->getPrice();

or access public members:

myShoe->m_price;

The constructor of the auto_ptr class accepts a normal pointer to an object of the class it is supposed to hold. It’s -> operator is overloaded, so it can be used like a normal pointer. The nice thing is its destructor. When called, it deletes the object it points to. This means, that once the auto_ptr dies, the object it pointed to goes away, too. No need to call delete. The semantics of this is, that an auto_ptr owns the object it points to. It’s lifetime is tied to the lifetime of the auto_ptr.”
Pete grows impatient “That’s all very nice, but how is this going to help me with my sink?”
Jack replies “Well, the auto_ptr has another nice feature. It can transfer ownership, using its copy-constructor. If you initialize one auto_ptr with another, ownership is transferred to the newly created auto_ptr

std::auto_ptr stillMyShoe(myShoe);

Now stillMyShoe points to your shoe-object and myShoe is empty. It points to NULL. The ownership of the shoe-object has been transferred from myShoe to stillMyShoe.”
Pete still doesn’t have a clue: “And?”
Jack goes on: “Well, what happens, if we change your function like this?

void saveShoeToDatabase(std::auto_ptr shoe);

And call it like this:

std::auto_ptr myShoe(new Shoe());
saveShoeToDatabase(myShoe);

Pete thinks for a moment, then replies “Well, when the method is called, the copy-constructor of auto_ptr is called, which … “. He pauses, his eyes widen as he goes on “ will transfer the ownership of the shoe-object from the caller to the sink!”
Jack smiles “Absolutely. And you don’t have to worry about forgetting to call delete or something. The auto_ptr is just a local variable in your function. This means that its destructor will be called when the function returns. This even works, when you return by throwing an exception. It’s almost like Java. Just faster! And you may of course use const, to make sure that you do not accidentally change the object, before it gets deleted!”

void saveShoeToDatabase(const std::auto_ptr shoe);

“There is just one drawback. An auto_ptr may point to NULL. So you will have to check for that in your function. But you don’t need any comments about how you handle memory. std::auto_ptr speaks for itself!”
Pete is satisfied and changes his code. Before commiting it, he drops a mail for Jeff and prepares him for the unpleasant fact, that his code will not compile any more. He tells him how to rewrite his code, to make it compile (and work) again.

———————-
One thing I forgot when writing this text is:
NEVER use auto_ptr in standard-containers, such as std::vector. It will get you in trouble, because for auto_ptr copies are not equivalent. Read more here: Using auto_ptr Effectively
Oh, and one last thing: Since memory managament is a delicate issue in C++, boost comes with a whole bunch of Smart Pointers: boost.smart_ptr

One thought on “Making function declarations talk: auto_ptr and memory management in C++

  1. Nicolas Luck

    Nice guide on how to write function declarations and use auto_ptr!
    I like that dialog style..;)

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.