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 = 10
    
    return 0;
}

函数对象的优势

相比于普通函数,函数对象有几个显著的优势:

  1. 可以保存状态:函数对象可以有成员变量,从而在多次调用之间保持状态。
  2. 可以被参数化:通过构造函数可以轻松地参数化函数对象的行为。
  3. 类型相关:每个函数对象类型都是唯一的,而普通函数指针则会丢失类型信息。
  4. 可内联:编译器通常能够内联函数对象的调用,提供更好的性能。
  5. 与STL算法兼容性好:STL算法设计为可以接受函数对象作为参数。

以下是一个展示函数对象保存状态的例子:

#include 
#include 
#include 

class Counter {
private:
    int count;
public:
    Counter() : count(0) {}
    
    // 函数调用运算符,计数并返回true
    bool 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: 5
    
    return 0;
}

STL中的预定义函数对象

STL在头文件中提供了一系列的预定义函数对象,这些函数对象执行常见的算术、比较和逻辑操作。

算术函数对象
  • std::plus: 加法,a + b
  • std::minus: 减法,a - b
  • std::multiplies: 乘法,a * b
  • std::divides: 除法,a / b
  • std::modulus: 取模,a % b
  • std::negate: 取反,-a
比较函数对象
  • std::equal_to: 等于,a == b
  • std::not_equal_to: 不等于,a != b
  • std::greater: 大于,a > b
  • std::less: 小于,a < b
  • std::greater_equal: 大于等于,a >= b
  • std::less_equal: 小于等于,a <= b
逻辑函数对象
  • std::logical_and: 逻辑与,a && b
  • std::logical_or: 逻辑或,a || b
  • std::logical_not: 逻辑非,!a

以下是这些预定义函数对象的使用示例:

#include 
#include 
#include 
#include 

int main() {
    // 算术函数对象
    std::plus<int> add;
    std::multiplies<int> multiply;
    
    std::cout << "5 + 3 = " << add(5, 3) << std::endl;           // 输出:5 + 3 = 8
    std::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.5
    
    return 0;
}
C++17中的透明函数对象

从C++14开始,STL中的函数对象模板可以不指定类型参数,称为透明函数对象(transparent function objects)。这些函数对象可以根据参数类型自动推导结果类型,无需显式指定模板参数。

#include 
#include 
#include 

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::string
    auto 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 23
    
    return 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 3
    
    return 0;
}

函数对象的继承和复合

STL允许通过继承和组合来创建更复杂的函数对象,特别是对于一元和二元函数类型。这些基本类型定义在头文件中:

  • 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::bind1ststd::bind2nd,但它们在C++11中已被弃用,在C++17中已被移除。

#include 
#include 
#include 
#include 

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 started
    printWithInfo("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;        // 输出:5
    std::cout << "reverseSubtract(10, 5) = " << reverseSubtract(10, 5) << std::endl;  // 输出:-5
    
    // 在算法中使用bind
    std::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;  // 输出:2
    
    return 0;
}
否定适配器:std::not_fn

C++17引入了std::not_fn函数适配器,它接受一个可调用对象并返回一个新的可调用对象,该对象返回原始可调用对象结果的逻辑否定。

#include 
#include 
#include 
#include 

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;  // 输出:5
    
    return 0;
}
成员函数适配器:std::mem_fn

std::mem_fn适配器可以将对象的成员函数转换为可调用对象,使其可以在STL算法中使用。

#include 
#include 
#include 
#include 

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::refstd::cref

std::refstd::cref是引用包装器,它们允许将引用传递给接受值的函数。std::ref创建一个对象引用的包装器,而std::cref创建一个对常量对象引用的包装器。

#include 
#include 

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变为6
    std::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;  // 仍然是6
    
    return 0;
}

自定义函数适配器

除了使用STL提供的函数适配器外,你还可以创建自己的函数适配器来满足特定需求。以下是一个简单的例子:

#include 
#include 
#include 
#include 

// 自定义函数适配器:重试执行函数
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表达式各有优缺点:

函数对象优势

  1. 可重用性:函数对象可以在多个地方使用,而Lambda表达式通常用于局部范围。
  2. 更多的状态管理:函数对象可以有更复杂的状态管理和生命周期控制。
  3. 清晰的接口:函数对象有明确定义的接口,包括参数类型和返回类型。
  4. 友好的调试:函数对象通常更容易调试,因为它们有明确的类型名称。

Lambda表达式优势

  1. 简洁性:Lambda表达式更简洁,不需要单独定义类。
  2. 就地定义:可以在需要的地方直接定义,提高代码的可读性。
  3. 自动捕获:可以方便地捕获局部变量。
  4. 类型推导:返回类型通常可以由编译器自动推导。

下面是一个对比示例:

#include 
#include 
#include 
#include 

// 使用函数对象
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::bind
    auto 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 
#include 
#include 
#include 

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 
#include 
#include 
#include 
#include 
#include 

// 数据处理管道:将多个处理函数串联成一个处理流水线
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 
#include 
#include 
#include 
#include 
#include 

// 简单事件系统
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 
#include 
#include 

// 基于是否有状态进行优化的函数
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替代已废弃的bind1stbind2nd
  • 使用std::not_fn替代已废弃的not1not2
  • 利用C++17的透明函数对象进行异构比较

总结

STL的函数对象和适配器为C++提供了强大的函数式编程能力。函数对象允许我们封装可调用的行为,带有状态和类型信息,而函数适配器则提供了转换和组合函数的方法。

主要内容回顾:

  1. 函数对象

    • 实现operator()的类/结构体,可像函数一样调用
    • 优势包括保持状态、类型安全、可内联等
    • STL提供的预定义函数对象涵盖了常见算术、比较和逻辑操作
  2. 函数适配器

    • 转换函数对象行为的工具
    • 常见STL适配器包括bindnot_fnmem_fn
    • 可以创建自定义适配器满足特殊需求
  3. 与Lambda表达式的比较

    • Lambda提供更简洁的语法,适合一次性使用
    • 函数对象适合复杂行为和需要在多处使用的场景
  4. 实际应用

    • 自定义排序
    • 数据处理管道
    • 事件系统
  5. 最佳实践

    • 根据需求选择适当的函数封装方式
    • 注意性能影响,特别是在频繁调用的代码中
    • 适当利用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++学习:六个月从基础到就业,c++,学习,开发语言)