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++
Programming with C++, Spring 2020, Lecture #20
By Aleksey Gurtovoy
Programming with C++, Spring 2020, Lecture #20
std::shared_ptr, associative containers: std::map
- 532