C++学习:六个月从基础到就业——STL:函数对象与适配器
C++学习:六个月从基础到就业——STL:函数对象与适配器
本文是我C++学习之旅系列的第二十九篇技术文章,也是第二阶段"C++进阶特性"的第八篇,主要介绍C++ STL中的函数对象与适配器。查看完整系列目录了解更多内容。
引言
在前面的STL系列文章中,我们已经深入探讨了STL的容器、迭代器和算法。这些组件构成了STL的基本架构,但还有一个重要的组成部分我们尚未详细介绍 —— 函数对象和适配器。这些元素为STL提供了强大的函数式编程能力,使得算法更加灵活和可定制。
函数对象(也称为仿函数,Functors)是一种行为类似函数的对象,它们可以被当作函数来调用。而函数适配器则用于转换现有的函数对象,使其接口符合特定的需求。这两个工具结合起来,为STL算法提供了高度的灵活性和可复用性,使得代码更加简洁、高效且易于维护。
本文将深入探讨函数对象和适配器的概念、类型、用法以及实际应用,帮助你掌握这些强大工具的使用方法。
函数对象(Functors)
什么是函数对象?
函数对象是一个实现了函数调用运算符(operator()
)的类或结构体的实例。由于它可以像函数一样被调用,因此也被称为仿函数。
// 基本函数对象示例
struct AddValue {int value;AddValue(int v) : value(v) {}// 函数调用运算符int operator()(int x) const {return x + value;}
};// 使用函数对象
int main() {AddValue addFive(5);int result = addFive(10); // 等同于调用addFive.operator()(10)std::cout << "10 + 5 = " << result << std::endl; // 输出:10 + 5 = 15// 创建临时函数对象并立即调用int anotherResult = AddValue(3)(7);std::cout << "7 + 3 = " << anotherResult << std::endl; // 输出:7 + 3 = 10return 0;
}
函数对象的优势
相比于普通函数,函数对象有几个显著的优势:
- 可以保存状态:函数对象可以有成员变量,从而在多次调用之间保持状态。
- 可以被参数化:通过构造函数可以轻松地参数化函数对象的行为。
- 类型相关:每个函数对象类型都是唯一的,而普通函数指针则会丢失类型信息。
- 可内联:编译器通常能够内联函数对象的调用,提供更好的性能。
- 与STL算法兼容性好:STL算法设计为可以接受函数对象作为参数。
以下是一个展示函数对象保存状态的例子:
#include <iostream>
#include <vector>
#include <algorithm>class Counter {
private:int count;
public:Counter() : count(0) {}// 函数调用运算符,计数并返回truebool operator()(int) {++count;return true;}// 获取当前计数int getCount() const {return count;}
};int main() {std::vector<int> nums = {1, 2, 3, 4, 5};Counter counter;// 使用count_if算法,但我们实际上只是想对元素进行计数std::count_if(nums.begin(), nums.end(), counter);std::cout << "Elements counted: " << counter.getCount() << std::endl;// 输出:Elements counted: 5return 0;
}
STL中的预定义函数对象
STL在<functional>
头文件中提供了一系列的预定义函数对象,这些函数对象执行常见的算术、比较和逻辑操作。
算术函数对象
std::plus<T>
: 加法,a + b
std::minus<T>
: 减法,a - b
std::multiplies<T>
: 乘法,a * b
std::divides<T>
: 除法,a / b
std::modulus<T>
: 取模,a % b
std::negate<T>
: 取反,-a
比较函数对象
std::equal_to<T>
: 等于,a == b
std::not_equal_to<T>
: 不等于,a != b
std::greater<T>
: 大于,a > b
std::less<T>
: 小于,a < b
std::greater_equal<T>
: 大于等于,a >= b
std::less_equal<T>
: 小于等于,a <= b
逻辑函数对象
std::logical_and<T>
: 逻辑与,a && b
std::logical_or<T>
: 逻辑或,a || b
std::logical_not<T>
: 逻辑非,!a
以下是这些预定义函数对象的使用示例:
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>int main() {// 算术函数对象std::plus<int> add;std::multiplies<int> multiply;std::cout << "5 + 3 = " << add(5, 3) << std::endl; // 输出:5 + 3 = 8std::cout << "5 * 3 = " << multiply(5, 3) << std::endl; // 输出:5 * 3 = 15// 在算法中使用比较函数对象std::vector<int> nums = {4, 1, 3, 5, 2};// 升序排序std::sort(nums.begin(), nums.end(), std::less<int>());std::cout << "Sorted ascending: ";for (int n : nums) std::cout << n << " ";std::cout << std::endl; // 输出:Sorted ascending: 1 2 3 4 5// 降序排序std::sort(nums.begin(), nums.end(), std::greater<int>());std::cout << "Sorted descending: ";for (int n : nums) std::cout << n << " ";std::cout << std::endl; // 输出:Sorted descending: 5 4 3 2 1// 使用C++14的透明函数对象(不需要模板参数)auto sum = std::plus<>{}(5, 3.5); // 注意类型推导std::cout << "5 + 3.5 = " << sum << std::endl; // 输出:5 + 3.5 = 8.5return 0;
}
C++17中的透明函数对象
从C++14开始,STL中的函数对象模板可以不指定类型参数,称为透明函数对象(transparent function objects)。这些函数对象可以根据参数类型自动推导结果类型,无需显式指定模板参数。
#include <iostream>
#include <functional>
#include <string>int main() {// 透明函数对象(不指定模板参数)auto add = std::plus<>{};std::cout << "5 + 3 = " << add(5, 3) << std::endl; // int结果std::cout << "5.5 + 3.2 = " << add(5.5, 3.2) << std::endl; // double结果std::string s1 = "Hello, ";std::string s2 = "world!";std::cout << "String concatenation: " << add(s1, s2) << std::endl; // 字符串连接// 用于关联容器的异构查找std::map<std::string, int, std::less<>> heterogeneous_map;heterogeneous_map["apple"] = 5;// 可以使用std::string_view查找,无需构造std::stringauto it = heterogeneous_map.find(std::string_view("apple"));if (it != heterogeneous_map.end()) {std::cout << "Found: " << it->first << " = " << it->second << std::endl;}return 0;
}
自定义函数对象
虽然STL提供了常见的函数对象,但在许多情况下,你可能需要创建自己的函数对象来满足特定需求。下面是一些自定义函数对象的例子:
1. 多参数函数对象
struct Point {int x, y;Point(int _x, int _y) : x(_x), y(_y) {}// 计算到原点的距离平方int distanceSquared() const {return x*x + y*y;}
};// 按到原点的距离比较点
struct ComparePointsByDistance {bool operator()(const Point& p1, const Point& p2) const {return p1.distanceSquared() < p2.distanceSquared();}
};// 使用示例
int main() {std::vector<Point> points = {{3, 4}, {1, 2}, {5, 12}, {9, 0}, {0, 7}};// 按到原点的距离排序std::sort(points.begin(), points.end(), ComparePointsByDistance());std::cout << "Points sorted by distance:" << std::endl;for (const auto& p : points) {std::cout << "(" << p.x << "," << p.y << ") - Distance: " << std::sqrt(p.distanceSquared()) << std::endl;}return 0;
}
2. 带状态的函数对象
// 生成序列的函数对象
class SequenceGenerator {
private:int current;int step;
public:SequenceGenerator(int start = 0, int step = 1) : current(start), step(step) {}int operator()() {int value = current;current += step;return value;}
};// 使用示例
int main() {std::vector<int> sequence(10);// 生成从5开始,步长为2的序列SequenceGenerator gen(5, 2);std::generate(sequence.begin(), sequence.end(), gen);std::cout << "Generated sequence: ";for (int n : sequence) std::cout << n << " ";std::cout << std::endl; // 输出:Generated sequence: 5 7 9 11 13 15 17 19 21 23return 0;
}
3. 可配置的函数对象
// 通过阈值过滤元素
template<typename T>
class ThresholdFilter {
private:T threshold;bool keepAbove; // true:保留大于阈值的元素,false:保留小于阈值的元素
public:ThresholdFilter(T threshold, bool keepAbove = true): threshold(threshold), keepAbove(keepAbove) {}bool operator()(const T& value) const {return keepAbove ? value > threshold : value < threshold;}
};// 使用示例
int main() {std::vector<int> values = {15, 5, 20, 10, 35, 15, 40, 3};// 保留大于20的元素auto it1 = std::copy_if(values.begin(), values.end(),std::ostream_iterator<int>(std::cout, " "),ThresholdFilter<int>(20, true));std::cout << std::endl; // 输出:35 40// 保留小于10的元素auto it2 = std::copy_if(values.begin(), values.end(),std::ostream_iterator<int>(std::cout, " "),ThresholdFilter<int>(10, false));std::cout << std::endl; // 输出:5 3return 0;
}
函数对象的继承和复合
STL允许通过继承和组合来创建更复杂的函数对象,特别是对于一元和二元函数类型。这些基本类型定义在<functional>
头文件中:
std::unary_function
(C++11已废弃,C++17已移除):为一元函数对象提供基本类型定义std::binary_function
(C++11已废弃,C++17已移除):为二元函数对象提供基本类型定义
虽然这些基类已被废弃,但了解它们的用途有助于理解旧代码和STL的设计原理。在现代C++中,应该使用类型特征(type traits)和模板来替代它们。
// 现代C++中的函数对象实现(不使用已废弃的基类)
template<typename T>
class Multiplier {
private:T factor;
public:using first_argument_type = T;using second_argument_type = T;using result_type = T;Multiplier(T f) : factor(f) {}T operator()(const T& value) const {return value * factor;}
};// 使用示例
int main() {std::vector<int> values = {1, 2, 3, 4, 5};std::vector<int> results(values.size());Multiplier<int> times3(3);std::transform(values.begin(), values.end(), results.begin(), times3);std::cout << "Original values: ";for (int n : values) std::cout << n << " ";std::cout << std::endl;std::cout << "After multiplying by 3: ";for (int n : results) std::cout << n << " ";std::cout << std::endl;return 0;
}
函数适配器(Function Adapters)
函数适配器是一种特殊的函数对象,它接受一个或多个函数对象并返回一个修改后的函数对象。它允许你转换函数的接口以适应特定的需求,例如绑定参数、组合函数等。
STL中的函数适配器
STL提供了多种函数适配器,尤其是在C++11及更高版本中,为函数式编程提供了强大的支持。
绑定适配器:std::bind
std::bind
是一个强大的函数适配器,它可以绑定函数参数,创建具有固定参数值的新函数对象。在C++11之前,STL提供了std::bind1st
和std::bind2nd
,但它们在C++11中已被弃用,在C++17中已被移除。
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>void printWithPrefix(const std::string& prefix, const std::string& str) {std::cout << prefix << ": " << str << std::endl;
}int main() {// 绑定第一个参数auto printWithDebug = std::bind(printWithPrefix, "DEBUG", std::placeholders::_1);auto printWithInfo = std::bind(printWithPrefix, "INFO", std::placeholders::_1);printWithDebug("System started"); // 输出:DEBUG: System startedprintWithInfo("Processing data"); // 输出:INFO: Processing data// 使用bind重新排列参数auto subtract = [](int a, int b) { return a - b; };auto reverseSubtract = std::bind(subtract, std::placeholders::_2, std::placeholders::_1);std::cout << "subtract(10, 5) = " << subtract(10, 5) << std::endl; // 输出:5std::cout << "reverseSubtract(10, 5) = " << reverseSubtract(10, 5) << std::endl; // 输出:-5// 在算法中使用bindstd::vector<int> values = {1, 15, 20, 25, 30, 35, 40};int threshold = 30;// 计数大于阈值的元素int count = std::count_if(values.begin(), values.end(),std::bind(std::greater<int>(), std::placeholders::_1, threshold));std::cout << "Values greater than " << threshold << ": " << count << std::endl; // 输出:2return 0;
}
否定适配器:std::not_fn
C++17引入了std::not_fn
函数适配器,它接受一个可调用对象并返回一个新的可调用对象,该对象返回原始可调用对象结果的逻辑否定。
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>bool isEven(int n) {return n % 2 == 0;
}int main() {std::vector<int> values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// 找到第一个偶数auto firstEven = std::find_if(values.begin(), values.end(), isEven);if (firstEven != values.end()) {std::cout << "First even number: " << *firstEven << std::endl; // 输出:2}// 使用not_fn找到第一个奇数auto firstOdd = std::find_if(values.begin(), values.end(), std::not_fn(isEven));if (firstOdd != values.end()) {std::cout << "First odd number: " << *firstOdd << std::endl; // 输出:1}// 计数奇数int oddCount = std::count_if(values.begin(), values.end(), std::not_fn(isEven));std::cout << "Number of odd values: " << oddCount << std::endl; // 输出:5return 0;
}
成员函数适配器:std::mem_fn
std::mem_fn
适配器可以将对象的成员函数转换为可调用对象,使其可以在STL算法中使用。
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>class Person {
public:std::string name;int age;Person(const std::string& n, int a) : name(n), age(a) {}bool isAdult() const {return age >= 18;}void introduce() const {std::cout << "My name is " << name << ", I am " << age << " years old." << std::endl;}
};int main() {std::vector<Person> people = {{"Alice", 25},{"Bob", 17},{"Charlie", 30},{"David", 14},{"Eve", 20}};// 使用mem_fn获取成员函数auto introduceFunc = std::mem_fn(&Person::introduce);auto isAdultFunc = std::mem_fn(&Person::isAdult);// 对每个人调用introduce方法std::for_each(people.begin(), people.end(), introduceFunc);// 计数成年人int adultCount = std::count_if(people.begin(), people.end(), isAdultFunc);std::cout << "Number of adults: " << adultCount << std::endl; // 输出:3// 按年龄排序auto getAge = std::mem_fn(&Person::age); // 获取age成员变量std::sort(people.begin(), people.end(), [&](const Person& p1, const Person& p2) {return getAge(p1) < getAge(p2);});std::cout << "\nPeople sorted by age:" << std::endl;for (const auto& p : people) {std::cout << p.name << ": " << p.age << std::endl;}return 0;
}
函数引用包装器:std::ref
和std::cref
std::ref
和std::cref
是引用包装器,它们允许将引用传递给接受值的函数。std::ref
创建一个对象引用的包装器,而std::cref
创建一个对常量对象引用的包装器。
#include <iostream>
#include <functional>void increment(int& value) {++value;
}void printValue(const int& value) {std::cout << "Value: " << value << std::endl;
}template<typename Func, typename Arg>
void executeFunction(Func f, Arg arg) {f(arg);
}int main() {int number = 5;// 使用std::ref传递引用executeFunction(increment, std::ref(number)); // number变为6std::cout << "After increment: " << number << std::endl;// 使用std::cref传递常量引用executeFunction(printValue, std::cref(number)); // 输出:Value: 6// 不使用std::ref,会创建副本executeFunction(increment, number); // 副本被修改,但number不变std::cout << "After increment without std::ref: " << number << std::endl; // 仍然是6return 0;
}
自定义函数适配器
除了使用STL提供的函数适配器外,你还可以创建自己的函数适配器来满足特定需求。以下是一个简单的例子:
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>// 自定义函数适配器:重试执行函数
template<typename Func>
class RetryAdapter {
private:Func func;int maxAttempts;
public:RetryAdapter(Func f, int attempts) : func(f), maxAttempts(attempts) {}template<typename... Args>auto operator()(Args&&... args) const {for (int attempt = 1; attempt < maxAttempts; ++attempt) {try {return func(std::forward<Args>(args)...);} catch (const std::exception& e) {std::cerr << "Attempt " << attempt << " failed: " << e.what() << std::endl;// 可以添加延迟逻辑}}// 最后一次尝试,如果再次失败则不捕获异常return func(std::forward<Args>(args)...);}
};// 辅助函数
template<typename Func>
auto retry(Func f, int maxAttempts) {return RetryAdapter<Func>(f, maxAttempts);
}// 测试函数
int unstableOperation(int value) {// 模拟可能失败的操作static int callCount = 0;++callCount;if (callCount % 3 != 0) { // 每三次调用成功一次throw std::runtime_error("Operation failed");}return value * 2;
}int main() {// 使用自定义重试适配器auto retryOperation = retry(unstableOperation, 5);try {int result = retryOperation(10);std::cout << "Operation succeeded with result: " << result << std::endl;} catch (const std::exception& e) {std::cout << "Operation failed after multiple attempts: " << e.what() << std::endl;}return 0;
}
函数对象与Lambda表达式的比较
C++11引入了Lambda表达式,它提供了一种创建匿名函数对象的简便方法。很多时候,Lambda可以替代传统函数对象,使代码更加简洁。然而,函数对象和Lambda表达式各有优缺点:
函数对象优势
- 可重用性:函数对象可以在多个地方使用,而Lambda表达式通常用于局部范围。
- 更多的状态管理:函数对象可以有更复杂的状态管理和生命周期控制。
- 清晰的接口:函数对象有明确定义的接口,包括参数类型和返回类型。
- 友好的调试:函数对象通常更容易调试,因为它们有明确的类型名称。
Lambda表达式优势
- 简洁性:Lambda表达式更简洁,不需要单独定义类。
- 就地定义:可以在需要的地方直接定义,提高代码的可读性。
- 自动捕获:可以方便地捕获局部变量。
- 类型推导:返回类型通常可以由编译器自动推导。
下面是一个对比示例:
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>// 使用函数对象
class MultiplyBy {
private:int factor;
public:MultiplyBy(int f) : factor(f) {}int operator()(int value) const {return value * factor;}
};void compareApproaches() {std::vector<int> numbers = {1, 2, 3, 4, 5};std::vector<int> results1(numbers.size());std::vector<int> results2(numbers.size());std::vector<int> results3(numbers.size());// 1. 使用传统函数对象MultiplyBy multiplyBy3(3);std::transform(numbers.begin(), numbers.end(), results1.begin(), multiplyBy3);// 2. 使用Lambda表达式std::transform(numbers.begin(), numbers.end(), results2.begin(),[factor = 3](int value) { return value * factor; });// 3. 使用std::bindauto multiplyFunc = std::bind(std::multiplies<int>(), std::placeholders::_1, 3);std::transform(numbers.begin(), numbers.end(), results3.begin(), multiplyFunc);// 比较结果std::cout << "Original numbers: ";for (int n : numbers) std::cout << n << " ";std::cout << std::endl;std::cout << "Using functor: ";for (int n : results1) std::cout << n << " ";std::cout << std::endl;std::cout << "Using lambda: ";for (int n : results2) std::cout << n << " ";std::cout << std::endl;std::cout << "Using std::bind: ";for (int n : results3) std::cout << n << " ";std::cout << std::endl;
}int main() {compareApproaches();return 0;
}
输出:
Original numbers: 1 2 3 4 5
Using functor: 3 6 9 12 15
Using lambda: 3 6 9 12 15
Using std::bind: 3 6 9 12 15
这个例子展示了三种实现同一功能的方法:使用函数对象、Lambda表达式和std::bind
。在简单的情况下,Lambda表达式通常是最简洁的选择,但对于更复杂的逻辑或需要在多处复用的代码,函数对象可能是更好的选择。
实际应用案例
案例1:自定义排序
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>struct Person {std::string name;int age;float height;Person(std::string n, int a, float h) : name(std::move(n)), age(a), height(h) {}
};// 多条件排序函数对象
class PersonComparator {
private:enum class SortCriteria { Name, Age, Height };SortCriteria primaryCriteria;SortCriteria secondaryCriteria;bool ascending;public:PersonComparator(SortCriteria primary = SortCriteria::Name,SortCriteria secondary = SortCriteria::Age,bool asc = true) : primaryCriteria(primary), secondaryCriteria(secondary), ascending(asc) {}bool operator()(const Person& p1, const Person& p2) const {// 根据主要排序条件比较int primaryResult = compare(p1, p2, primaryCriteria);if (primaryResult != 0) {return ascending ? primaryResult < 0 : primaryResult > 0;}// 相等时,使用次要排序条件int secondaryResult = compare(p1, p2, secondaryCriteria);return ascending ? secondaryResult < 0 : secondaryResult > 0;}private:int compare(const Person& p1, const Person& p2, SortCriteria criteria) const {switch (criteria) {case SortCriteria::Name:return p1.name.compare(p2.name);case SortCriteria::Age:return p1.age - p2.age;case SortCriteria::Height:return p1.height < p2.height ? -1 : (p1.height > p2.height ? 1 : 0);default:return 0;}}
};int main() {std::vector<Person> people = {{"Alice", 25, 165.5f},{"Bob", 30, 180.0f},{"Charlie", 25, 175.5f},{"David", 35, 182.3f},{"Alice", 28, 170.0f}};// 按名字升序,相同名字按年龄升序std::sort(people.begin(), people.end(), PersonComparator(PersonComparator::SortCriteria::Name, PersonComparator::SortCriteria::Age, true));std::cout << "Sorted by name (ascending), then age:" << std::endl;for (const auto& p : people) {std::cout << p.name << ", " << p.age << " years, " << p.height << " cm" << std::endl;}// 按年龄降序,相同年龄按身高降序std::sort(people.begin(), people.end(), PersonComparator(PersonComparator::SortCriteria::Age, PersonComparator::SortCriteria::Height, false));std::cout << "\nSorted by age (descending), then height:" << std::endl;for (const auto& p : people) {std::cout << p.name << ", " << p.age << " years, " << p.height << " cm" << std::endl;}return 0;
}
案例2:数据转换管道
#include <iostream>
#include <vector>
#include <string>
#include <functional>
#include <algorithm>
#include <numeric>// 数据处理管道:将多个处理函数串联成一个处理流水线
template<typename T>
class Pipeline {
private:std::function<T(T)> processFunction;public:// 默认构造函数:创建一个不做任何处理的管道Pipeline() : processFunction([](const T& input) { return input; }) {}// 从函数构造template<typename Func>Pipeline(Func func) : processFunction(func) {}// 应用管道处理一个值T operator()(const T& input) const {return processFunction(input);}// 连接另一个处理函数,形成新的管道template<typename Func>Pipeline<T> then(Func func) const {auto currentFunc = processFunction;return Pipeline<T>([currentFunc, func](const T& input) {return func(currentFunc(input));});}// 批量处理一个容器template<typename Container>Container process(const Container& inputs) const {Container outputs;outputs.reserve(inputs.size());std::transform(inputs.begin(), inputs.end(), std::back_inserter(outputs), processFunction);return outputs;}
};// 字符串处理函数
std::string toUpper(const std::string& s) {std::string result = s;std::transform(s.begin(), s.end(), result.begin(), ::toupper);return result;
}std::string addPrefix(const std::string& s) {return "PREFIX_" + s;
}std::string addSuffix(const std::string& s) {return s + "_SUFFIX";
}std::string truncate(const std::string& s) {return s.length() > 20 ? s.substr(0, 20) + "..." : s;
}int main() {std::vector<std::string> inputs = {"hello world", "functional programming", "pipeline pattern","this is a very long string that needs truncation"};// 创建处理管道Pipeline<std::string> processPipeline = Pipeline<std::string>(toUpper).then(addPrefix).then(addSuffix).then(truncate);// 处理单个值std::string result = processPipeline("test string");std::cout << "Single result: " << result << std::endl;// 批量处理auto results = processPipeline.process(inputs);std::cout << "\nBatch results:" << std::endl;for (const auto& r : results) {std::cout << r << std::endl;}return 0;
}
案例3:事件系统
#include <iostream>
#include <vector>
#include <string>
#include <functional>
#include <map>
#include <memory>// 简单事件系统
class EventSystem {
public:using EventHandler = std::function<void(const std::string&, const void*)>;private:std::map<std::string, std::vector<EventHandler>> eventHandlers;public:// 注册事件处理器void addEventListener(const std::string& eventName, EventHandler handler) {eventHandlers[eventName].push_back(handler);}// 触发事件void dispatchEvent(const std::string& eventName, const void* eventData = nullptr) {auto it = eventHandlers.find(eventName);if (it != eventHandlers.end()) {for (const auto& handler : it->second) {handler(eventName, eventData);}}}// 移除所有事件处理器void clearEventListeners(const std::string& eventName) {eventHandlers[eventName].clear();}
};// 示例组件
class UIComponent {
private:std::string name;public:UIComponent(std::string n) : name(std::move(n)) {}void handleClick(const std::string& eventName, const void* data) {std::cout << name << " received " << eventName << " event" << std::endl;}// 转换成事件处理器EventSystem::EventHandler getClickHandler() {return std::bind(&UIComponent::handleClick, this, std::placeholders::_1, std::placeholders::_2);}
};// 示例数据类
struct MouseEvent {int x, y;bool leftButton, rightButton;MouseEvent(int _x, int _y, bool left, bool right) : x(_x), y(_y), leftButton(left), rightButton(right) {}
};// 通用事件处理函数
void logEvent(const std::string& eventName, const void* data) {std::cout << "Event logged: " << eventName << std::endl;
}int main() {EventSystem eventSystem;// 注册通用事件处理器eventSystem.addEventListener("click", logEvent);eventSystem.addEventListener("mousemove", logEvent);// 创建UI组件UIComponent button("Button");UIComponent checkbox("Checkbox");// 注册组件特定的事件处理器eventSystem.addEventListener("click", button.getClickHandler());eventSystem.addEventListener("click", checkbox.getClickHandler());// 创建事件数据MouseEvent clickEvent(100, 150, true, false);// 触发事件std::cout << "Dispatching click event:" << std::endl;eventSystem.dispatchEvent("click", &clickEvent);std::cout << "\nDispatching mousemove event:" << std::endl;MouseEvent moveEvent(120, 160, false, false);eventSystem.dispatchEvent("mousemove", &moveEvent);return 0;
}
最佳实践和性能考量
使用函数对象和适配器时,需要注意以下最佳实践和性能考量:
1. 选择适当的方法
根据具体情况选择最合适的方法:
- 对于简单的一次性函数,使用Lambda表达式
- 对于复杂或可重用的功能,使用函数对象
- 对于现有函数的简单调整,使用函数适配器
2. 避免过度使用函数适配器
函数适配器(特别是std::bind
)可能会产生额外的开销。在性能关键代码中,可能需要直接实现功能而不是使用多层次的适配器。
3. 利用内联优化
函数对象的一个主要优势是它们可以被编译器内联,因此应该保持函数调用运算符简单,以便于编译器优化。
// 良好风格:简单的函数对象适合内联
struct GoodFunctor {bool operator()(int x) const { return x % 2 == 0; }
};// 不良风格:复杂的实现不易内联
struct BadFunctor {bool operator()(int x) const {if (x < 0) x = -x;int result = 0;for (int i = 0; i < 10; ++i) {result += (x + i) % 3;}return result % 2 == 0;}
};
4. 状态管理考虑
函数对象的状态管理需要特别注意:
- 确保状态修改是线程安全的(如果在并发环境中使用)
- 考虑状态的所有权和生命周期问题
- 适当使用const标记不修改状态的操作
5. 使用类型特征优化
针对不同类型的函数对象,可以使用类型特征进行特化和优化:
#include <iostream>
#include <type_traits>
#include <functional>// 基于是否有状态进行优化的函数
template<typename Func, bool HasState = !std::is_empty<Func>::value>
class OptimizedExecutor;// 特化:无状态函数对象
template<typename Func>
class OptimizedExecutor<Func, false> {
public:template<typename... Args>auto execute(Args&&... args) const {std::cout << "Executing stateless functor" << std::endl;return Func()(std::forward<Args>(args)...);}
};// 特化:有状态函数对象
template<typename Func>
class OptimizedExecutor<Func, true> {
private:Func func;public:OptimizedExecutor(Func f) : func(f) {}template<typename... Args>auto execute(Args&&... args) const {std::cout << "Executing stateful functor" << std::endl;return func(std::forward<Args>(args)...);}
};// 测试用函数对象
struct StatelessAdder {int operator()(int a, int b) const { return a + b; }
};class StatefulMultiplier {
private:int factor;public:StatefulMultiplier(int f) : factor(f) {}int operator()(int value) const { return value * factor; }
};int main() {// 使用无状态函数对象OptimizedExecutor<StatelessAdder> adder;std::cout << "Result: " << adder.execute(5, 3) << std::endl;// 使用有状态函数对象StatefulMultiplier multiplier(3);OptimizedExecutor<StatefulMultiplier> executor(multiplier);std::cout << "Result: " << executor.execute(7) << std::endl;return 0;
}
6. 针对现代C++的调整
随着C++的发展,一些以前的做法已经被更好的替代:
- 使用Lambda表达式替代简单的函数对象
- 使用
auto
简化类型声明 - 使用
std::bind
替代已废弃的bind1st
和bind2nd
- 使用
std::not_fn
替代已废弃的not1
和not2
- 利用C++17的透明函数对象进行异构比较
总结
STL的函数对象和适配器为C++提供了强大的函数式编程能力。函数对象允许我们封装可调用的行为,带有状态和类型信息,而函数适配器则提供了转换和组合函数的方法。
主要内容回顾:
-
函数对象:
- 实现
operator()
的类/结构体,可像函数一样调用 - 优势包括保持状态、类型安全、可内联等
- STL提供的预定义函数对象涵盖了常见算术、比较和逻辑操作
- 实现
-
函数适配器:
- 转换函数对象行为的工具
- 常见STL适配器包括
bind
、not_fn
、mem_fn
等 - 可以创建自定义适配器满足特殊需求
-
与Lambda表达式的比较:
- Lambda提供更简洁的语法,适合一次性使用
- 函数对象适合复杂行为和需要在多处使用的场景
-
实际应用:
- 自定义排序
- 数据处理管道
- 事件系统
-
最佳实践:
- 根据需求选择适当的函数封装方式
- 注意性能影响,特别是在频繁调用的代码中
- 适当利用C++的类型系统和优化特性
掌握函数对象和适配器是成为C++高级程序员的重要一步,它们提供了编写灵活、可复用和高性能代码的能力,特别是在配合STL算法使用时更能发挥强大作用。
参考资源
- 《C++标准库》by Nicolai M. Josuttis
- 《Effective STL》by Scott Meyers
- 《Modern C++ Design》by Andrei Alexandrescu
- cppreference.com - C++函数对象文档
- C++ Core Guidelines - F部分:函数
这是我C++学习之旅系列的第二十九篇技术文章。查看完整系列目录了解更多内容。
如有任何问题或建议,欢迎在评论区留言交流!
相关文章:
C++学习:六个月从基础到就业——STL:函数对象与适配器
C学习:六个月从基础到就业——STL:函数对象与适配器 本文是我C学习之旅系列的第二十九篇技术文章,也是第二阶段"C进阶特性"的第八篇,主要介绍C STL中的函数对象与适配器。查看完整系列目录了解更多内容。 引言 在前面的…...
Linux基础篇、第四章_02磁盘及分区管理fdisk 和 gdisk
题目:Linux 磁盘及分区管理 版本号: 1.0,0 作者: 老王要学习 日期: 2025.04.25 适用环境: Centos7 文档说明 本教程适用于 Centos7 环境,详细介绍 Linux 磁盘及分区管理操作。包含虚拟机添加磁盘的关机与开机添加方法、MBR 和 GPT 两种分区方式特点、…...
火山云的市场竞争
火山云是字节跳动旗下的云计算服务,对吧?那它的竞争对手应该包括国内外的大型云服务提供商。首先,国际市场上,像AWS、Azure、Google Cloud这些巨头肯定是大头。国内的话,阿里云、腾讯云、华为云这些应该都是主要的竞争…...
创建型设计模式之:简单工厂模式、工厂方法模式、抽象工厂模式、建造者模式和原型模式
创建型设计模式之:简单工厂模式、工厂方法模式、抽象工厂模式、建造者模式和原型模式 (一)简单工厂模式 简单工厂模式将对象的实例化过程封装到一个工厂类中,根据输入的条件创建不同类型的对象。 角色划分: 抽象产品…...
【Linux内核设计与实现】第三章——进程管理01
文章目录 1. 引言2. 进程&线程——概念3. 进程控制块/进程描述符(PCB)4. 进程内核栈(Kernel Stack)4.1. 进程内核栈的定义4.2. thread_info 体系结构相关进程描述4.3. 定位进程描述符(task_struct)和内核栈以及内核栈指针的问题 5. 进程 IDÿ…...
正大模型视角下的市场结构判断逻辑
正大模型视角下的市场结构判断逻辑 在多数交易策略中,结构识别往往先于方向判断。以正大的数据研判风格为例,其核心逻辑是:价格行为不能孤立解读,必须结合时间与成交效率来判断当前结构的有效性。 例如,一个上涨过程&…...
4.25学习——文件上传之00截断
继昨天学习的基础文件上传内容,进一步学习文件上传的绕过方式 00截断绕过 原理:00截断是操作系统层的漏洞,由于操作系统是C语言或汇编语言编写的,这两种语言在定义字符串时,都是以\0(即0x00)作…...
黑马Redis(三)黑马点评项目
优惠卷秒杀 一、全局唯一ID 基于Redis实现全局唯一ID的策略: Component RequiredArgsConstructor public class RedisIdWorker {private static final Long BEGIN_TIMESTAMP1713916800L;private static final int COUNT_BITS 32;Resourceprivate final StringRed…...
dedecms织梦arclist标签noflag属性过滤多个参数
织梦dedecms系统arclist标签noflag属性默认是只能过滤一个参数,比如过滤推荐是noflagc,过滤有图片的文章是noflagc,在模板制作过程中,有时候我们为了seo和避免重复,需要过滤多个参数。今天小编就来跟大家讲讲织梦dedec…...
Jira、PingCode、Redmine等18款缺陷管理工具对比评测
本文主要介绍了以下:1. PingCode; 2. Worktile; 3. Jira; 4. Bugzilla; 5. TAPD; 6. 码云; 7. Redmine; 8. Trac; 9. 蓝鲸智云; 10. 阿里云效等等18款缺陷管理工具。 在现代软件开发和项目管理中,缺陷管理工具扮演着至关重要的角色。随着企业对软件质量的…...
京东以图搜图(拍立淘)API接口返回参数详解
京东以图搜图(拍立淘)API接口的返回参数通常以结构化JSON格式呈现,涵盖商品基础信息、相似度评分、库存状态及扩展字段,以下为关键参数详解及使用建议: 一、核心返回参数解析 状态标识类 status:请求状态…...
LSTM+KNN - 多元数据异常检测 !
大家好!我是我不是小 upper~ 今天想和大家分享一个超实用的案例:如何通过 LSTM 与 KNN 实现多元数据异常检测。 想象一下,在工厂的智能化监控场景中,各类传感器实时采集着温度、湿度、压力等海量数据。我们的目标,就是从中精准识别出设备潜在故障等异常情况。 LSTM 作为时…...
OpenHarmony之电源管理子系统公共事件定义
OpenHarmony之电源管理子系统公共事件定义 电源管理子系统面向应用发布如下系统公共事件,应用如需订阅系统公共事件,请参考公共事件接口文档。 COMMON_EVENT_BATTERY_CHANGED 表示电池充电状态、电平和其他信息发生变化的公共事件的动作。 值&#x…...
angular 实现可编辑可选择复制的表格
这个实现的核心就是ag-grid 当然有类似的库就不必多说,React, Vue和纯h5类似。简单贴一下代码 1.首先是h5部分,就一个id为supply-chain-material-grid-table的div,记住要设置高度 <div class"dki-supply-chain-page-body">…...
组织用户数统计实现
# 完整的组织用户数统计实现 完整的组织用户数统计实现,包括模拟SQL查询、完整的Java代码实现以及详细解释。 ## 1. 模拟SQL查询 假设我们有一个组织表(organization)和用户表(user),以下是模拟查询SQL: sql -- 获取各组织及其用户数量&a…...
天机学堂day10作业,完善兑换优惠券功能
UserCouponServiceImpl /*** 兑换码兑换优惠券* param code*/TransactionalOverridepublic void exchangeCoupon(String code) {//1、校验code是否为空if (StringUtils.isBlank(code)) {throw new BadRequestException("非法参数!");}//2、解析兑换码&…...
Python编程的真谛:超越语法,理解编程本质
你是否也曾陷入这样的误区:学了无数的 Python 语法、刷了几十套题,写起代码却仍然卡顿、举步维艰?这时候你才发现,真正阻碍进步的,从不是语法,而是你对“编程本质”的理解。 如果你只是死记硬背Python的语…...
C语言 函数补充
目录 static和extern函数 1.static和extern函数 static和extern都是C语言中的关键字 static 是 静态的 的意思,可以用来: - 修饰局部变量- 修饰全局变量- 修饰函数 extern 是用来声明外部符号的。 在讲解 static 和 extern 之前再讲一下: 作用域和生命周期。 …...
【AI图像创作变现】04实操路径—插图/绘本/创意图集
引言 如果说头像是“一个角色的起点”,那么插图、绘本和图集就是“这个角色能走多远”。相比于头像这种单图任务,插图类创作更强调批量性、叙事性与风格统一性,它既可以承载故事,也可以构成一套完整的内容产品结构。 这类任务特…...
Lesar: 面向 Lustre/Scade 语言的形式化模型检查工具
在《同步反应式系统》的第一课中,介绍了同步数据流语言 Lustre 生态中的形式化模型检查器 Lesar 的用法。Lesar 可对 lustre v4 语言以及 Scade 语言中部分数据流核心特性进行模型检查。 Lesar 介绍 Lesar 是 Verimag 研发维护的形式化方法模型检查工具。该工具的理…...
告别 “幻觉” 回答:RAG 中知识库与生成模型的 7 种对齐策略
一、引言 大语言模型(LLM)在文本生成领域展现出惊人能力,但 “幻觉” 问题(生成虚构或偏离事实的内容)始终是落地应用的核心挑战。检索增强生成(RAG)通过将外部知识库与 LLM 结合,形…...
【Web应用服务器_Tomcat】一、Tomcat基础与核心功能详解
在 Java Web 应用开发领域,Apache Tomcat 是一座不可或缺的基石。作为一款开源、轻量级的 Servlet 容器和 Web 服务器,Tomcat 以其稳定可靠、易于部署和高度可定制性,被广泛应用于各类 Web 应用的部署与运行。 一、Tomcat 简介 Tomcat 是…...
Cesium实现地形可视域分析
Cesium实现可视化分析 一、地形可视域主要实现技术(Ray + 地形碰撞检测) Cesium 本身的 Ray 类可以用来执行非常精确的射线检测,我们可以结合地形高度(sample)来逐点检测光线是否与 terrain 相交,从而判断是否可见。 1.1 优势 实时判断每条射线是否被 terrain 遮挡地形…...
Java—— 常见API介绍 第五期
JDK8以后新增的时间相关类 Date类ZoneId:时区Instant:时间戳ZoneDateTime:带时区的时间 日期格式化类 SimpleDateFormat DateTimeFormatter:用于时间的格式化和解析 日历类 Calendar LocalDate:年、月、日LocalTime…...
ViewPager FragmentPagerAdapter在系统杀死应用后重建时UI不刷新的问题
解决方案 通过重写getItemId方法,返回Fragment的hashCode: Override public long getItemId(int position) {/*** 恢复状态重建时,新的 Fragment 不刷新UI。* 原因:instantiateItem 中通过 mFragmentManager.findFragmentByTag(…...
第3讲、大模型如何理解和表示单词:词嵌入向量原理详解
1. 引言 大型语言模型(Large Language Models,简称LLM)如GPT-4、Claude和LLaMA等近年来取得了突破性进展,能够生成流畅自然的文本、回答复杂问题、甚至编写代码。但这些模型究竟是如何理解人类语言的?它们如何表示和处…...
关于STM32f1新建工程
创建文件夹 首先创建一个存放工程的文件夹,建议建立在D,E盘 新建工程 在kiel5里面 找到刚刚建立的文件夹,然后在此文件夹里面新建一个文件夹用来存放本次工程,文件夹可以根据工程内容所编写,然后给自己工程也就是…...
Linux:进程间通信---匿名管道
文章目录 1. 进程间通信1.1 什么是进程间通信?1.2 为什么进程要进行进程间通信?1.3 怎么实现进程间通信? 2. 匿名管道2.1 匿名管道的原理2.2 匿名管道的系统接口2.3 匿名管道的使用2.4 匿名管道的运用场景 序:在上一篇文章中我们知…...
python代做推荐系统深度学习知识图谱c#代码代编神经网络算法创新
以下是针对推荐系统、深度学习、知识图谱和神经网络算法创新的代码框架及开发建议,适用于C#和Python的跨语言协作项目。以下内容分为几个部分,涵盖技术选型、代码示例和创新方向。 1. 推荐系统(Python C#) Python部分࿰…...
【动手学大模型开发】VSCode 连接远程服务器
Visual Studio Code(VSCode)是一款由微软开发的免费、开源的现代化代码编辑器。它以其轻量级、高性能和广泛的编程语言支持而受到开发者的青睐。VSCode 的核心特点包括: 跨平台:支持 Windows、macOS 和 Linux 操作系统。扩展市场…...
PostgreSQL 漏洞信息详解
PostgreSQL 漏洞信息详解 PostgreSQL 作为一款开源关系型数据库,其安全漏洞会被社区及时发现和修复。以下是 PostgreSQL 漏洞相关的重要信息和资源。 一、主要漏洞信息来源 1. 官方安全公告 PostgreSQL 安全信息页面:https://www.postgresql.org/sup…...
华为L410上制作内网镜像模板:在客户端配置模板内容
华为L410上制作内网镜像模板:在客户端配置模板内容 在本教程中,我们将继续在华为L410上配置内网镜像模板,具体介绍如何在客户端设置以便于在首次开机时自动安装软件。我们将主要使用WeChat作为示例。 1. 制作镜像模板,开启 rc.l…...
分布式队列对消息语义的处理
在分布式系统中,消息的处理语义(Message Processing Semantics)是确保系统可靠性和一致性的关键。有三种语义: 在分布式系统中,消息的处理语义(Message Processing Semantics)是确保系统可靠性和…...
《免费开放”双刃剑:字节跳动Coze如何撬动AI生态霸权与暗涌危机?》
战略动机分析 降低技术门槛为数据采集接口 Coze平台宣称**“30秒无代码生成AI Bot”,大幅降低了企业开发AI应用的技术门槛。任何不懂编程的业务人员都可以通过可视化流程和提示词,在半分钟内搭建聊天机器人或智能代理。这种极低门槛意味着更多企业和个人…...
AI 开发工具提示词集体开源!解锁 Cursor、Cline、Windsurf 等工具的核心逻辑
✨ 前言:提示词,AI 编程工具的灵魂 随着大模型编程能力的迅速提升,AI 编程工具如雨后春笋般涌现,涵盖了从代码编辑器(如 Cursor、Windsurf、Cline)到应用生成服务(如 Lovable、Bolt.new、V0&am…...
MYSQL 常用字符串函数 和 时间函数详解
一、字符串函数 1、CONCAT(str1, str2, …) 拼接多个字符串。 SELECT CONCAT(Hello, , World); -- 输出 Hello World2、SUBSTRING(str, start, length) 或 SUBSTR() 截取字符串。 SELECT SUBSTRING(MySQL, 3, 2); -- 输出 SQ3、LENGTH(str) 与 CHAR_LENGTH…...
Ubuntu 下 Nginx 1.28.0 源码编译安装与 systemd 管理全流程指南
一、环境与依赖准备 为确保编译顺利,我们首先更新系统并安装必要的编译工具和库: sudo apt update sudo apt install -y build-essential \libpcre3 libpcre3-dev \zlib1g zlib1g-dev \libssl-dev \wgetbuild-essential:提供 gcc、make 等基…...
线程怎么创建?Java 四种方式一网打尽
🚀 Java 中线程的 4 种创建方式详解 创建方式实现方式是否推荐场景说明1. 继承 Thread 类class MyThread extends Thread❌ 不推荐简单学习、单线程场景2. 实现 Runnable 接口class MyRunnable implements Runnable✅ 推荐更适合多线程共享资源3. 实现 Callable 接…...
高效使用DeepSeek对“情境+ 对象 +问题“型课题进行开题!
目录 思路"情境 对象 问题"型 课题选题的类型有哪些呢?这要从课题题目的构成说起。通过对历年来国家社会科学基金立项项目进行分析,小编发现,课题选题类型非常丰富,但一般是围绕限定词、研究对象和研究问题进行不同的组…...
【GCC bug】libstdc++.so.6: version `GLIBCXX_3.4.29‘ not found
在 conda 环境安装 gcc/gxx 之后,运行开始遇到了以下的报错 File "/mnt/data/home/xxxx/miniforge3/envs/GAGAvatar/lib/python3.12/site-packages/google/protobuf/internal/wire_format.py", line 13, in <module>from google.protobuf import de…...
python卸载报错:No Python 3.12 installation was detected已解决
问题背景 在卸载Python 3.12.5时,遇到了一个棘手的问题:运行安装包python.exe点击Uninstall后,系统提示No Python 3.12 installation was detected. 尝试了网上各种方法(包括注册表清理、修复repair,卸载unins…...
【Hive入门】Hive分区与分区表完全指南:从原理到企业级实践
引言 在大数据时代,高效管理海量数据成为企业面临的核心挑战。Hive作为Hadoop生态系统中最受欢迎的数据仓库解决方案,其分区技术是优化数据查询和管理的关键手段。本文将全面解析Hive分区技术的原理、实现方式及企业级最佳实践,帮助您构建高性…...
AI之FastAPI+ollama调用嵌入模型OllamaBgeEmbeddings
以下是对该 FastAPI 代码的逐行解析和详细说明: 代码结构概览 from fastapi import Depends # 导入依赖注入模块def get_embedder():return OllamaBgeEmbeddings(base_url="http://ollama-cluster:11434",timeout=30,max_retries=5)@app.post("/embed")…...
RK3588芯片NPU的使用:yolov8-pose例子图片检测在安卓系统部署与源码深度解析(rknn api)
一、本文的目标 将yolo8-pose例子适配安卓端,提供选择图片后进行姿态识别功能。通过项目学习源码和rknn api。二、开发环境说明 主机系统:Windows 11目标设备:搭载RK3588芯片的安卓开发板核心工具:Android Studio Koala | 2024.1.1 Patch 2,NDK 27.0三、适配(迁移)安卓 …...
【HTTP/3:互联网通信的量子飞跃】
HTTP/3:互联网通信的量子飞跃 如果说HTTP/1.1是乡村公路,HTTP/2是现代高速公路系统,那么HTTP/3就像是一种革命性的"传送门"技术,它彻底重写了数据传输的底层规则,让信息几乎可以瞬间抵达目的地,…...
2024 年:Kubernetes 包管理的新前沿
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:历代文学,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程,高并发设计…...
SIEMENS PLC 程序 GRAPH 程序解读 车型入库
1、程序载图1 2、程序截图2 3、程序解释 这是一个基于西门子 GRAPH 编程的车型 1 入库顺序控制流程图,通过状态机结构(状态框 S 与转移条件 T)描述完整工作流程,具体如下: 整体流程概述 初始化:从 S1&am…...
c++11新特性随笔
1.统一初始化特性 c98中不支持花括号进行初始化,编译时会报错,在11当中初始化可以通过{}括号进行统一初始化。 c98编译报错 c11: #include <iostream> #include <set> #include <string> #include <vector>int main() {std:…...
微信小程序文章管理系统开发实现
概述 在内容为王的互联网时代,高效的文章管理系统成为各类平台的刚需。幽络源平台今日分享一款基于SSM框架开发的微信小程序文章管理系统完整解决方案,该系统实现了多角色内容管理、智能分类、互动交流等功能。 主要内容 一、用户端功能模块 多角…...
3种FSC标签你用对了吗?
如果你留意过产品上的FSC小树标识,也许会发现它们很相似但又各不相同。 根据产品使用的FSC认证材料的不同比例,共有三种不同类型的FSC标签: 1、FSC 100% 所有使用的材料均来自负责任管理的FSC认证森林。 标签文本为:“ From well-…...