In this document, we discuss how to effectively code with the C++ Standard Template Library (STL) while maintaining high-quality code. Thus we list some, best practices related to code structure, code safety, and code generality. These practices are key to writing clean, efficient, and maintainable C++ code that leverages the power of STL effectively.
std::vector
, std::array
, std::list
, and smart pointers (e.g., std::shared_ptr
, std::unique_ptr
) handle memory management, boundary checking, and resource allocation more safely than raw pointers or C-style arrays.std::vector
instead of raw arrays for dynamic-sized collections.std::array
for fixed-size arrays instead of C-style arrays.new
/delete
); prefer RAII (Resource Acquisition Is Initialization) and use STL containers or smart pointers.cpp
std::vector<int> vec = {1, 2, 3, 4}; // Dynamic array
std::array<int, 4> arr = {1, 2, 3, 4}; // Fixed-size array
for
loops provide a cleaner, more readable syntax for iterating over STL containers, reducing potential off-by-one errors and avoiding direct access to iterators unless necessary.const auto&
form to avoid unnecessary copying, especially for non-primitive types.```cpp
std::vector
for (const auto& word : words) { // const reference avoids copy std::cout << word << std::endl; } ```
std::find
, std::sort
, std::transform
, etc.) are highly optimized and reduce boilerplate code. They also increase readability and often eliminate common looping errors.std::sort
instead of a custom sort.cpp
std::vector<int> vec = {4, 2, 3, 1};
std::sort(vec.begin(), vec.end()); // Sort instead of writing a loop
Example with std::transform
and Lambda:
cpp
std::vector<int> nums = {1, 2, 3};
std::transform(nums.begin(), nums.end(), nums.begin(), [](int x) { return x * 2; });
const
Wherever Possibleconst
ensures immutability, leading to safer code. It prevents unintended modifications and makes the code easier to reason about.const
if they won’t be modified.const
iterators (std::vector<int>::const_iterator
) when you only need to read elements.const
reference to avoid unnecessary copies.cpp
const int size = 10; // `size` is constant, can’t be changed
cpp
void printVector(const std::vector<int>& vec) { // Passed as const reference
for (const auto& v : vec) {
std::cout << v << std::endl;
}
}
std::unique_ptr
or std::shared_ptr
Over Raw Pointersstd::unique_ptr
for exclusive ownership and std::shared_ptr
when ownership is shared among multiple entities.std::unique_ptr
by default for single ownership.std::shared_ptr
for shared ownership but be cautious about cyclic references.cpp
std::unique_ptr<int> ptr = std::make_unique<int>(42); // Exclusive ownership
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42); // Shared ownership
std::vector
provides random access iterators, std::list
provides bidirectional iterators, etc.std::sort
, std::binary_search
) only with containers that support random access iterators (e.g., std::vector
, std::deque
).cpp
std::vector<int> vec = {1, 2, 3};
std::sort(vec.begin(), vec.end()); // Works with random-access iterators (vector)
emplace_back
Over push_back
for Efficiencyemplace_back
constructs an object in-place directly in the container, avoiding unnecessary copies or moves, which improves performance, especially for complex objects.emplace_back
instead of push_back
for constructing elements in place in containers like std::vector
, std::deque
.cpp
std::vector<std::pair<int, std::string>> vec;
vec.emplace_back(1, "example"); // Constructs in place, avoids copy
std::vector
for fast access and when you primarily insert/remove from the end.std::deque
for frequent insertions/removals from both ends.std::list
for fast insertions/removals in the middle, but avoid it if random access is needed.std::set
and std::map
for ordered collections with fast (O(log n)) insertions and lookups.std::unordered_set
and std::unordered_map
for fast (O(1) average) lookups with no ordering requirements.```cpp
// Example: Prefer vector for simple dynamic arrays
std::vector
// Example: Prefer set when uniqueness and sorting are required
std::set
std::remove_if
followed by erase
.std::map
and std::set
, iterator invalidation is more restricted, but still be careful with modifications.```cpp
std::vector
// Safe way to remove elements during iteration vec.erase(std::remove_if(vec.begin(), vec.end(), { return x % 2 == 0; }), vec.end()); ```
std::move
to Avoid Unnecessary Copiesstd::move
allows for efficient resource transfer, avoiding unnecessary deep copies of objects. This is especially useful for large or expensive-to-copy objects.std::move
when transferring ownership of resources, such as moving elements into a container or returning objects from a function.```cpp
std::vector
// Move s
into the vector, avoiding a copy
vec.push_back(std::move(s));
```
std::initializer_list
for Convenient Initializationstd::initializer_list
allows for convenient and readable initialization of containers and classes with a set of values.Practices**: - Use initializer lists to simplify container initialization and custom class constructors.
cpp
std::vector<int> vec = {1, 2, 3}; // Automatically uses initializer_list
std::bad_alloc
), and it’s important to handle exceptions properly to ensure robust code.By following these best practices, your students will develop a solid foundation for writing clean, efficient, and maintainable C++ code using the STL. The focus should be on leveraging STL's powerful abstractions, improving code safety with modern features (like smart pointers and range-based loops), and writing generic, reusable code by using algorithms and iterators effectively.
Previous Page | Course Schedule | Course Content