Alexander Stepanov wrote a library for C++ based on templates.
This was the original Standard Template Library (STL).
At the November 1993 meeting, it was presented to the standard committee for standardization--this eventually became the C++ Standard Library. The Standard Library has since expanded massively over the STL, which is still developed.
Like many other things in the programmingverse, an inordinate number of bytes have been wasted arguing over whether "STL" or "C++ Standard Library" is the correct term for this library software.
I will prefer "standard library" in these slides, but I often use "STL" when speaking casually.
Of these, we have seen a surprising number in lecture already!
Additionally, some of these are fairly niche headers which we'd need to teach another class first to discuss properly.
<chrono> and <tuple>
Contains basic functions and classes for calculating time, such as:
As well as some convenience utilities, like
int main()
{
auto start = std::chrono::steady_clock::now();
std::cout << "f(42) = " << fibonacci(42) << '\n';
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
auto elapsed_nanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed_seconds);
}
You just use clock() and measure the results, right?
12. Time always progresses monotonically forward.
25. One minute on the system clock has exactly the same duration as one minute on any other clock.
26. Ok, but the duration of one minute on the system clock will be pretty close to the duration of one minute on most other clocks.
27. Fine, but the duration of one minute on the system clock would never be more than an hour.
28. You can’t be serious.
There's a challenge that some of the old UNIX programmers used to throw around: take two datetimes (date + time + timezone info) and determine which one occurred earlier.
This challenge is nearly impossible to do correctly on first try.
auto data = std::make_tuple(age, name, height)
Functional languages and Python abuse the hell out of these structures.
Why doesn't C++ use them as much? Two reasons:
std::tuple<int, string, double> tup = get_data();
Let's say I have the following variable...
std::tuple<int, string, double> tup = get_data();
How do I access the i-th element of the tuple?
string name = tup[1];
string name = std::get<1>(tup);
How do I destructure the tuple?
int age;
string name;
double height;
std::tie(age, name, height) = tup;
string name = tup.at(1);
You can use the so-called "structured binding" to deconstruct a tuple in-place as follows:
auto [age, name, height] = tup;
You can even use this in other contexts, which is awesome!
std::map <char, int> charFrequency;
/* Populate map with frequencies */
for (auto [c, freq] : charFrequency){
assert(charFrequency[c] == freq);
}
Example: suppose in Project 1, you wanted to create a list of all collisions in the world. Each element of the list should contain the colliding object along with the colliding point, like so:
collisions = [(Box 1, Ball, point1), (Box1, Box2, point2) .... ]
collisions = [(Box 1, Ball, point1), (Box1, Box2, point2) .... ]
You could write a class for this, but if you're just going to break it back apart again, that's just a class name cluttering up your namespace.
Instead, use a tuple to temporarily group these objects together!
std::vector<std::tuple<GameObject, GameObject, Point>> collisions; // Please just use auto
Note: if you find yourself writing lots of functions taking in tuples, maybe consider writing a class. Tuples in C++ are most effective when they're temporary structures, not core data representations.
<iostream> and <fstream>
Modify individual files (file access)
Modifying relationships between files (filesystem access)
If you need a way to access the filesystem in pre-C++17, use a library like TinyDir.
Three steps to any file operation:
Three steps to any file operation:
Opening A File
Reading/Writing File
Closing A File
We can open a file using the stream constructors.
using std::istream;
using std::ostream;
using std::fstream;
int main(){
istream f1("file1.txt");
ostream f2("file2.txt");
fstream f3("file3.txt",
fstream::out | fstream::append);
}
We can read/write files using the bitshift operators
std::ostream f("test.csv");
// Print a table as CSV format
for(const auto& row : table){
for(double val : row){
f << val << ',';
}
f << '\n';
}
???
RAII takes care of this for us!
It may seem like magic that the shift operators just work with streams, but it's not!
These operators are user-defined.
std::ostream &operator<<(std::ostream &os, const Stack &stack) {
os << "Stack( " << std::hex;
if (!stack.empty()) {
os << stack.front();
for (std::size_t i = 1, e = stack.size(); i < e; ++i)
os << "; " << stack[i];
}
os << std::dec << " )";
return os;
}
One note: the standard stipulates that all operations on a vector should be O(1) except for insert, assign, and erase, which can be O(n).
A Double-Ended Queue.
Very similar to a vector except that you get an O(1) push_front() and pop_front().
Vectors store their data internally as one contiguous piece of memory, reallocating if needed to get a larger memory piece.
dequeues often store their data internally as several smaller chunks, meaning less reallocation at the cost of increased traversal cost.
If you only ever need to access your data from the ends, consider a dequeue. Otherwise, vector should be more than good enough.
Implementation of linked list (double and single, respectively) in C++.
Essentially the same interface as the vector/dequeue!
The only difference is how these methods are implemented. The list classes guarantee that insert is O(1) anywhere, instead of just at both ends (dequeue) or the back (vector).
A: Alright, alright.
There are certain applications where dequeue and lists do offer a speed advantage...but in general, people are very bad at predicting when this is the case!
Suppose I need to read in a list of numbers (which is unsorted during input), and store them in sorted order. I don't know how long the list is in advance.
What data structure would be best suited for this task?
Max vector time (100k elements) is 0.26s, a tree-based approach is under 0.01s
(we may see some examples of exceptions next week--game companies are infamous for not wanting to use the standard library)
Comparison | Hashing | |
---|---|---|
No | map set |
unordered_map unordered_set |
Yes | multimap multiset |
unordered_multimap unordered_multiset |
A set of decisions around policy balloon this into 8 different associative containers
The API for <set> is pretty similar.
template < typename Key,
typename T,
typename Compare = less<Key>,
typename Alloc = allocator<pair<const Key,T> >
> class map;
What are all these pieces used for?
map stores the key/value pairs in an ordered data structure, usually a red-black tree
Let's say we want to create a map that maps tags to dogs
class NameTag{
string name;
Address addr;
Material m;
};
std::map<NameTag, Dog> m;
NameTag t;
Dog d;
m[t] = d;
In file included from /usr/include/c++/9/string:48,
from test.cpp:1:
/usr/include/c++/9/bits/stl_function.h: In instantiation of ‘constexpr bool std::less<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = NameTag]’:
/usr/include/c++/9/bits/stl_map.h:497:32: required from ‘std::map<_Key, _Tp, _Compare, _Alloc>::mapped_type& std::map<_Key, _Tp, _Compare, _Alloc>::operator[](const key_type&)
test.cpp:23:8: required from here
/usr/include/c++/9/bits/stl_function.h:386:20: error: no match for ‘operator<’ (operand types are ‘const NameTag’ and ‘const NameTag’)
386 | { return __x < __y; }
| ~~~~^~~~~
In file included from /usr/include/c++/9/bits/stl_algobase.h:64,
from /usr/include/c++/9/bits/char_traits.h:39,
from /usr/include/c++/9/string:40,
from test.cpp:1:
/usr/include/c++/9/bits/stl_pair.h:454:5: note: candidate: ‘template<class _T1, class _T2> constexpr bool std::operator<(const std::pair<_T1, _T2>&, const std::pair<_T1, _T2>&)’
454 | operator<(const pair<_T1, _T2>& __x, const pair<_T1, _T2>& __y)
| ^~~~~~~~
In file included from /usr/include/c++/9/string:48,
from test.cpp:1:
/usr/include/c++/9/bits/stl_function.h: In instantiation of
‘constexpr bool std::less<_Tp>::operator()(const _Tp&, const _Tp&) const
[with _Tp = NameTag]’:
/usr/include/c++/9/bits/stl_map.h:497:32: required from ‘std::map
error: no match for ‘operator<’ (operand types are ‘const NameTag’
and ‘const NameTag’)
386 | { return __x < __y; }
Translation: I don't know how to implement less<T> for T = NameTag
There are many ways to do this!
bool NameTag operator<(const NameTag& other){
// Implementation
}
using ntcompare = std::function<bool(const NameTag&, const NameTag&)>;
ntcompare compare_tags = [](const NameTag& a, const NameTag& b){ /* ... */ };
std::map<NameTag, Dog, ntcompare>(compare_tags);
template<
class Key,
class T,
class Hash = std::hash<Key>,
class KeyEqual = std::equal_to<Key>,
class Allocator = std::allocator< std::pair<const Key, T> >
> class unordered_map;
Java uses the term "TreeMap" and "HashMap" for the concepts of "map" and "unordered_map", respectively.
This means that you can easily iterate over the entries in sorted order.
This makes them well-suited to applications where lookup time is a major bottleneck.
Benchmark both and decide! If you can't spare the time to benchmark, then it probably doesn't actually matter that much.
The containers we've seen so far work by defining their own storage:
Container Adaptors don't do this--instead, they wrap an existing class to provide ADT functionality.
template<
class T,
class Container = std::deque<T>
> class stack;
stack<int> s1;
stack<int, std::vector> s2;
Read the project guidelines carefully. I will most likely not have time to do regrades for this project.
*subject to change