The University of Iowa
The College of Liberal Arts and Sciences
Department of Computer Science
Lecture/Lab #20
std::shared_ptr, associative containers: std::map
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";
}
A "smart" pointer with shared ownership
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;
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.
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
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.
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.
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";