Writing Correct C++ Programs without “delete” Huang-Ming Huang CSE332 Guest Lecture Washington...

Preview:

Citation preview

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.