Writing Correct C++ Programs without
“delete”
Huang-Ming HuangCSE332 Guest Lecture
Washington University in St. Louis
Computer Architecture
int main() { int i = 0; Card c1; ….
return 0;}
Memory Model
Code
Stack
Heap
int main() { int i = 0; Card c1; ….
return 0;}
Global
int func() { int i = 1; int j = 2; const char* str=“test”; Card a(“3C”); Card b=a; ….
return 0;}
Card::Card(const char* s) { ….
}
Program Execution Model
Code SectionStack Section
i
j
2
a.rank
a.suit
3
str
1CLUBS0
Card::Card(): rank(NO_RANK), suit(NO_SUIT) {}
b.rank NO_RANKNO_SUIT
3CLUBSb.suit
Card b; b=a;
Card& Card::operator= (const Card& other) {
rank= other.rank;
suit = other.suit;
}
Card::~Card() {}
0x12345678
Program Execution Model with Heap Allocation
Game* makeGame(const char* name) { Game* result = 0; if (strcmp(name,”bridge”)==0) result= new BridgeGame; else if (strcmp(name,”hearts”)==0) result= new HeartsGame; return result;}
CodeStackHeap
0x4518ab31
hearts
name
0result
0x34567890
0x4518ab31
int strcmp(const char* s1, const char* s2){ …. …. return result;}10
1. Find an empty space in heap for HeartsGame. (Time consuming)
2. Execute the constructor of HeartsGame3. Return the address of the newly created
object
0x40123456
0x40123456
0x40123456
Differentiate Stack and Heap Objects
Card a_card; // stack object Destructor will be implicitly called when a_card is
out of scope. Card* card_ptr = new Card; // heap object
Requires explicit delete Don’t do these
delete &a_card; a_card = *(new Card);
When to use “new”? Objects have to live beyond current scope
Is object copying a viable solution? Good for copy : Card objects
Object size is small Non-polymorphic
Bad for copy : Player, Game objects. Involve heap memory allocation Polymorphic
Is object swapping a good alternative? Good for objects with embedded heap objects such as
Player, std::string All STL containers, like
std::vector, std::set, std::map, …, etc. Not useful for polymorphic classes
Game* makeGame(const char* name) { Game* result = 0; if (strcmp(name,”bridge”)==0) result= new BridgeGame; else if (strcmp(name,”hearts”)==0) result= new HeartsGame; return result;}
Problem with the interaction between “new” and exception
Player* Game::add_player(const char* name) {
Player* p = std::find_if(players,player+size, lessThan(name));
if (p != player+size && p->name() ==name)
return p;
Player* temp = new Player[size+1];
Player* dest = std::copy(players, p, temp);
*dest = Player(name);
std::copy(p, player+size, dest+1);
std::swap(temp, players);
delete[] temp;
return dest;
}players+size
name=“Ken”
pplayers TedSueJoeBob
p
tempTedSue
dest
Ken
JoeBob
Could throw exceptions
temp won’t be deleted if any exception is thrown
How to fix it?
Use “try” and “catch”, delete temp in catch clause if any exception is thrown.
If non-thrown swap() is defined for Player class, you can use swap() instead of assignment. Only deal with std::bad_alloc exception Not a solution for other possible exceptions
Resource Acquisition is Initialization (RAII)
Resource Acquisition is Initialization (RAII) Referred as “Guard Idiom” by Dr. Gill.
However, the term RAII is more widely used in C++ community.
Relies on Stack object would be destructed upon out of
scope. Use stack object to hold the ownership of a heap
object, or any other resource that requires explicit clean up.
Heap object ( or resources) is release upon the destruction of the RAII stack object.
RAII Utility Classes
template <class T> class scoped_ptr { T* ptr;public : scoped_ptr(T* p=0) :ptr(p) {} ~scoped_ptr() { delete ptr; } void reset(T* p) { delete ptr; ptr = p; } T* get() { return ptr;} void swap(scoped_ptr& );}
template <class T> class vector { T* ptr; size_t sz;public : vector() :ptr(0), sz(0) {} ~ vector () { delete ptr[]; } … void swap(vector& );}
How to use RAII
Game* game;
if (strcmp (argv[1], “bridges”)==0) game = new BridgeGame ;else if (strcmp(argv[1],”hearts”)==0) game = new HeartsGame ;
game->add_player(“tom”);game->add_player(“ted”);
game->refresh();game->deal_hands();game->score_hands();game->print();
delete game;
boost::scoped_ptr<Game> game;
if (strcmp (argv[1], “bridges”)==0) game.reset(new BridgeGame);else if (strcmp(argv[1],”hearts”)==0) game.reset(new HeartsGame);
game->add_player(“tom”);game->add_player(“ted”);
game->refresh();game->deal_hands();game->score_hands();game->print();
Stock Smart Pointers
Boost (www.boost.org) scoped_ptr shared_ptr weak_ptr
std auto_ptr
boost::scoped_ptr
Used for a single object, not for array Noncopyable
scoped_ptr<Game> game1(new BridgeGame); // OK
scope_ptr<Game> game2(game1); // Won’t compile
scoped_ptr<Game> game3; // OK
game3 = game1; // Won’t compile
game3.reset(new BridgeGame); // OK
game3.swap(game1); // OK
Useful to implement pointer to implementation (pimple) idiom.
C++ Pimple idiom(Conceptual View)
+method1()+method2()
<<interface>>Foo
+method1()+method2()
-state
<<implementation class>>FooImpl
1
-pimpl
1
pimpl->method1();
C++ Pimple idiom(implementation View)
/// Foo.h#include <boost/scoped_ptr.hpp>
struct FooImpl; // forward declarationclass Foo { using namespace boost; scoped_ptr<FooImpl> pimpl; public: Foo(); void method1();};
/// Foo.cc#include “Foo.h”
struct FooImpl { int state; …};
Foo::Foo() : pimpl(new FooImpl) {}
void Foo::method1() { ++ pimpl->state; …}
Why use pimple idiom
Separation between interface and implementation Allows library vendors keep implementation detail
secrete Avoid client recompilation for the change of
internal data structure. Exception safety
std::auto_ptr
Similar to scoped_ptr, but copyable. Copy represents ownership transfer. Best used for function parameters and return
types in the case when ownership transfer is needed.
auto_ptr<Game> makeGame(const char* name) {if (strcmp(name, “bridge”) ==0)
return auto_ptr<Game>(new BridgeGame); else if (strcmp(name, “hearts”) == 0)
return auto_ptr<Game>(new HeartsGame); return auto_ptr<Game>();}
boost:shared_ptr• Reference counting semantics.• Simplest form of garbage collection
a : share_ptr<Foo>
ptrpcount :Foo
1 : int
-ptr : T*-pcount : int*
shared_ptr
T
Class ViewObject View
b : share_ptr<Foo>
ptrpcount
shared_ptr<Foo> a(new Foo);shared_ptr<Foo> b = a;
2 : int0 : int
Reference Counting and Cyclic Dependency Problem
Deparment Course
1*
belongs to
offers
class Department { vector<share_ptr<Course> > courses; }
class Course { share_ptr<Course> department; }
Reference Counting and Cyclic Dependency Problem
void fun() { shared_ptr<Department> cse(new Department); shared_ptr<Course> cse332(new Course); cse332.department = cse; cse.courses.push_back(cse332);}
csecse332
: Department : Course
1 : int 1 : int
courses department
2 : int
courses[0]
2 : int
Boost::weak_ptr
Used together with shared_ptr to avoid cyclic reference counting problem.
Does not increase reference count during construction and copying.
Class Department { vector<shared_ptr<Course> > courses;};
Class Course { weak_ptr<Department> department;};
void fun() { shared_ptr<Department> cse(new Department); shared_ptr<Course> cse332(new Course); cse332.department = cse; cse.courses.push_back(cse332);}
The reference count is not incremented
Collection of Objects Player* players = new Player[size];
Need explicit delete statement for players array vector<Player> players;
Expensive Player object copying vector<Player*> players;
May require explicit delete statement for each individual Player object
vector<scoped_ptr<Player> > and vector<auto_ptr<Player> > won’t compile. because all containers in C++ standard requires that any
element a and b in the container, After a = b then a == b
Collection of Objects
vector<boost::shared_ptr<Player> > players Ownership to Player objects are not exclusive Comparing to vector<Player*>, it incurs small overhead for
reference counting. No need for explicit delete statement.
boost::ptr_vector<Player> players; Only heap objects can be added to the container. It has the exclusively ownership for the objects it
contains. No need for explicit delete statement.
Preferred Form for Object Collections
vector<int>, vector<Card> Contained objects are cheap to copy
ptr_vector<Player>, ptr_vector<Game> Contained objects are expensive to copy or
polymorphic. vector<shared_ptr<Player> > or
vector<share_ptr<Game> > The ownership of the contained object cannot be
exclusively held.
Summary
Dynamic memory allocation are expensive, avoid it if you can.
Be careful with implicit object copying. Don’t write code that requires explicit “delete”
statement. For single object : use smart pointers For collection of value types : use standard
container classes For collection of pointers : use boost ptr
containers or shared_ptr.
Top Related