C++ Standard Library utility
In the C++ standard library,<utility>The header file contains some practical utility classes and functions, which are very useful when writing efficient and readable code.
utilityThe core value of the library lies in:
- Providing fundamental data structures and utility functions
- Simplifying the implementation of common programming tasks
- Providing foundational support for other standard library components
utilityAlthough the header file is small, the tools it provides are very practical:
| Components/Functions | Purpose | Use Case |
|---|---|---|
std::pair |
Store two related values | Functions returning multiple values, map elements |
std::make_pair |
Conveniently creating pairs | Template type deduction, simplifying code |
std::swap |
Swap two values | Algorithm implementation, sorting operations |
std::move |
Enable move semantics | Resource management, performance optimization |
std::forward |
Perfect Forwarding | Universal references, template programming |
Core components explained
std::pair: key-value pair container
std::pairYesutilitypair: the most commonly used component in the library, used to combine two values into a single object.
Basic Syntax
#include <utility> // 创建 pair 对象的基本方式 std::pair<类型1, 类型2> 变量名(值1, 值2);
Example
#include <utility>
#include <string>
int main() {
// Method 1: Direct initialization
std::pair<int, std::string> student1(101, "Alice");
// Method 2: Using the make_pair function (recommended)
auto student2 = std::make_pair(102, "Bob");
// Method 3: Deduction guide supported since C++17
std::pair student3(103, "Charlie");
// Access the members of pair
std::cout << "Student ID: " << student1.first << ", Name: " << student1.second << std::endl;
return 0;
}
Common operations on pair
Example
#include <iostream>
void pairOperations() {
// Create a pair
std::pair<int, double> p1(10, 3.14);
std::pair<int, double> p2(20, 2.71);
// Comparison operations
if (p1 < p2) {
std::cout << "p1 is less than p2" << std::endl;
}
// Assignment operation
p1 = p2;
// Memberwise assignment supported since C++11
int a;
double b;
std::tie(a, b) = p1; // Assign the value of p1 to a and b respectively
// C++17 structured binding (more concise)
auto [x, y] = p1;
std::cout << "x = " << x << ", y = " << y << std::endl;
}
std::make_pair: convenient creation function
std::make_pairstd::make_pair: a template function that can automatically deduce types, simplifying the process of creating pairs.
Example
#include <iostream>
#include <string>
void demonstrateMakePair() {
// Automatic type deduction, no need to explicitly specify template parameters
auto p1 = std::make_pair(42, "Hello");
auto p2 = std::make_pair(3.14, true);
// Especially convenient for use in containers
std::vector<std::pair<int, std::string>> students;
students.push_back(std::make_pair(101, "Alice"));
students.push_back(std::make_pair(102, "Bob"));
for (const auto& student : students) {
std::cout << "Student ID: " << student.first
<< ", Name: " << student.second << std::endl;
}
}
Utility functions
std::swap: swap two values
std::swapUsed to swap the values of two objects of the same type.
Example
#include <iostream>
void demonstrateSwap() {
int a = 10, b = 20;
std::cout << "Before swap: a = " << a << ", b = " << b << std::endl;
std::swap(a, b);
std::cout << "After swap: a = " << a << ", b = " << b << std::endl;
// Can also be used for custom types (if move semantics are implemented)
std::string str1 = "Hello", str2 = "World";
std::swap(str1, str2);
std::cout << "String swap: " << str1 << " " << str2 << std::endl;
}
std::move: move semantics support
std::moveUsed to convert an object into an rvalue reference, enabling move semantics.
Example
#include <iostream>
#include <vector>
void demonstrateMove() {
std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2;
std::cout << "Before move - v1 size: " << v1.size()
<< ", v2 size: " << v2.size() << std::endl;
// Use move semantics to transfer resource ownership
v2 = std::move(v1);
std::cout << "After move - v1 size: " << v1.size()
<< ", v2 size: " << v2.size() << std::endl;
// v1 is now in a valid but unspecified state
// Normally, v1 should not be used again unless reassigned
}
std::forward: perfect forwarding
std::forwardUsed to implement perfect forwarding, preserving the value category of arguments.
Example
#include <iostream>
// Ordinary function - cannot preserve value category
template<typename T>
void normalFunction(T arg) {
std::cout << "Ordinary function parameter" << std::endl;
}
// Function using perfect forwarding
template<typename T>
void perfectForwardingFunction(T&& arg) {
// Preserve the parameter's original value category (lvalue or rvalue)
normalFunction(std::forward<T>(arg));
}
void demonstrateForward() {
int x = 10;
// Pass an lvalue
perfectForwardingFunction(x);
// Pass an rvalue
perfectForwardingFunction(20);
}
Integer sequence utilities (C++14)
C++14 introduced integer sequence utilities, primarily used for template metaprogramming.
std::integer_sequence
Example
#include <iostream>
// Use an integer sequence to print each value in the sequence
template<typename T, T... Ints>
void print_sequence(std::integer_sequence<T, Ints...>) {
// Use a fold expression (C++17) to print all values
((std::cout << Ints << " "), ...);
std::cout << std::endl;
}
void demonstrateIntegerSequence() {
// Create an integer sequence
auto seq = std::integer_sequence<int, 1, 2, 3, 4, 5>();
print_sequence(seq);
// Use make_integer_sequence to generate the sequence
auto seq2 = std::make_integer_sequence<int, 5>();
print_sequence(seq2); // Output: 0 1 2 3 4
}
Real-world application examples
Example 1: Function returning multiple values
Example
#include <iostream>
#include <cmath>
// Function returns multiple values: computation result and error code
std::pair<double, bool> calculateSqrt(double number) {
if (number < 0) {
return std::make_pair(0.0, false); // Error case
}
return std::make_pair(std::sqrt(number), true); // Success case
}
void multipleReturnValues() {
auto result1 = calculateSqrt(16.0);
if (result1.second) {
std::cout << "Square root: " << result1.first << std::endl;
} else {
std::cout << "Calculation error: cannot take the square root of a negative number" << std::endl;
}
auto result2 = calculateSqrt(-4.0);
if (!result2.second) {
std::cout << "Calculation error: cannot take the square root of a negative number" << std::endl;
}
}
Case 2: Application in STL containers
Example
#include <map>
#include <iostream>
#include <string>
void mapWithPair() {
// Each element of std::map is a std::pair
std::map<int, std::string> studentMap;
// Insert key-value pair
studentMap.insert(std::make_pair(101, "Alice"));
studentMap.emplace(102, "Bob"); // More efficient way
// Iterate over the map
for (const auto& [id, name] : studentMap) {
std::cout << "Student ID: " << id << ", Name: " << name << std::endl;
}
// Find element
auto it = studentMap.find(101);
if (it != studentMap.end()) {
std::cout << "Found student: " << it->second << std::endl;
}
}
Example 3: Implementing a simple dictionary
Example
#include <vector>
#include <iostream>
#include <algorithm>
class SimpleDictionary {
private:
std::vector<std::pair<std::string, std::string>> entries;
public:
void addWord(const std::string& word, const std::string& meaning) {
entries.emplace_back(word, meaning);
}
std::pair<bool, std::string> findMeaning(const std::string& word) {
for (const auto& [w, m] : entries) {
if (w == word) {
return std::make_pair(true, m);
}
}
return std::make_pair(false, "");
}
void printAll() {
for (const auto& [word, meaning] : entries) {
std::cout << word << ": " << meaning << std::endl;
}
}
};
void dictionaryExample() {
SimpleDictionary dict;
dict.addWord("apple", "a fruit");
dict.addWord("book", "an object used for reading");
auto result = dict.findMeaning("apple");
if (result.first) {
std::cout << "Meaning: " << result.second << std::endl;
}
}
Best practices and considerations
1. Use auto to simplify pair creation
Example
auto student = std::make_pair(101, "Alice");
// Not recommended: explicitly specifying the type (more verbose)
std::pair<int, std::string> student(101, "Alice");
2. Prefer emplace over insert
Example
// Recommended: use emplace (more efficient)
myMap.emplace(1, "one");
// Not recommended: use insert (requires constructing a temporary object)
myMap.insert(std::make_pair(1, "one"));
3. Correct use of move semantics
Example
std::string str = "a very large string";
// Correct: use move on return
return str; // The compiler will automatically optimize, no need for explicit move
}
void processString(std::string str) {
// Handle strings
}
void usageExample() {
std::string largeStr = "very large data";
// Correct: use move when passing
processString(std::move(largeStr));
// Note: largeStr should no longer be used
}
4. Use of structured bindings
Example
std::pair<int, std::string> getStudent() {
return {101, "Alice"};
}
void structuredBindingExample() {
// Traditional way
auto student = getStudent();
int id = student.first;
std::string name = student.second;
// Modern way (C++17)
auto [id2, name2] = getStudent(); // More concise and clear
}