The University of Iowa
The College of Liberal Arts and Sciences
Department of Computer Science

Programming Languages and Tools:

CS:3210:0001

Lecture/Lab #20

Programming with C++

std::shared_ptr, associative containers: std::map

Warm up

What will be the output of the following program?

struct car
{
    car( std::string const& make_and_model )
        : make_and_model( make_and_model ) {}
    ~car() {
        std::cout << "Destroying " << make_and_model << "\n"; 
    }

    std::string const make_and_model;
};

int main() {
    std::vector<std::unique_ptr<car>> cars;
    cars.push_back( std::make_unique<car>( "Tesla Model X" ) );
    cars.push_back( std::make_unique<car>( "Honda Accord" ) );

    cars[0] = std::make_unique<car>( "Hyundai Sonata" );
    for ( auto const& c : cars )
        std::cout << c->make_and_model << "\n";
}

std::shared_ptr

A "smart" pointer with shared ownership

  • Represents a pointer to a dynamically allocated object
  • Automatically manages the object's life time:
  • Enables run-time polymorphism for collections of objects and factory functions
  • Is copyable
  • Pointers to derived classes are implicitly convertible to pointers to the corresponding base class
  • No specific shared_ptr owns the object.
  • All shared_ptrs pointing to a specific object "collaborate" to ensure the object is destructed when it’s no longer needed.
  • When the last shared_ptr pointing to an object stops pointing there, that shared_ptr destroys the object it points to.

std::shared_ptr (cont.)

std::shared_ptr<T> p;

Creates a shared_ptr instance that doesn't point to/manage any object.

auto p = std::make_shared<T>( ... )

Dynamically allocates a new object of type T by calling a corresponding constructor, takes ownership of the newly created object.

if ( p )

Returns true if p owns an object.

*p

If p manages an object, returns a reference to the managed object, otherwise the behavior is undefined.

p->method( ... );

If p manages an object, calls the specified method of the managed object, otherwise the behavior is undefined.

auto q = p;

Object ownership is now shared between p and q.

q = p;

shared_ptr vs unique_ptr

  • shared_ptr is copyable, unique_ptr is moveable
  • shared_ptr is slightly more expensive (due to the overhead of choreographing shared ownership)
  • Both can be used in containers
  • shared_ptr can be created from unique_ptr, but not vice versa:
auto u = std::make_unique<car>( "Honda Accord" );
std::shared_ptr<car> s = u; // error
std::shared_ptr<car> s2 = std::move( u ); // OK
std::unique_ptr<car> u2 = std::move( s ); // nope, can't do

Guideline

Use unique_ptr by default unless/until you need shared ownership.

std::map

A collection of key–value pairs

auto word_count( std::istream& in )
{
    std::map<std::string, int> result;
    std::string word;
    while ( in >> word )
        ++result[word];
    return result;
}
  • std::map is an ordered associative container, similar to Python's dict / Java's TreeMap

  • std::map<K, V> defines a mapping between keys of type K and values of types V

  • Given std::map<K, V> m; definition, m[key] returns a reference to the corresponding mapped value, adds (key, V()) to the map if key does not already exist

  • By default elements are ordered using operator < on the map's keys

std::map (cont.)

map<K,V> m = { { k1, v1 }, { k2, v2 }, ..., { kn, vn } };

Defines a mapping between keys of type K and values of types V, initializes it with specific pairs of keys and values.

m[k]

Returns a reference to the value that is mapped to a key k, adds of a new mapping { k, V() } if no mapping for k exist yet.

m.empty()

Returns true if m is empty.

m.insert( { k, v } )

Adds a mapping between k and v to m if key k does not already have an associated mapping, otherwise a no-op.

m.size()

Returns number of elements in m.

m.insert_or_assign( k, v )

Adds a mapping between k and v to m if key k does not already have an associated mapping, otherwise overwrites the existing mapping with the new one.

std::map (cont.)

m.count( k )

Returns the number of elements with key k.

for ( auto [k, v] : m ) ...

Iterates through m's elements ({ k, v } pairs) in the order defined by m's key comparison function (by default operator<)

for ( auto p : m ) {

    // p.first == k

    // p.second == v

}

m.clear()

Erases all elements from m.

m.erase( k )

Erases an element with the key k, if any.

std::map vs Python's dict

phonebook = { 
    "jack": 4098, 
    "sape": 4139 
}

phonebook["guido"] = 4127
for k, v in phonebook.items():
    print(k, v)

phonebook = dict([
    ( "sape", 4139 ), 
    ( "guido", 4127 ), 
    ( "jack", 4098 )
])

del phonebook["sape"]
print( "sape" in phonebook )
map<string,int> phonebook = { 
    { "jack", 4098 }, 
    { "sape", 4139 }
};

phonebook["guido"] = 4127;
for ( auto [k, v]: phonebook )
    cout << k << " " << v << "\n";

phonebook = {
    { "sape", 4139 },
    { "guido", 4127 },
    { "jack", 4098 }
};

phonebook.erase( "sape" );
cout << phonebook.count( "sape" ) 
     << "\n";

Python

C++

Made with Slides.com