面向对象设计模式在C++中的实践:策略模式与现代C++11/14特性结合

在现代C++中,设计模式与语言特性相结合,能够让代码既具备高度可维护性,又不失性能与简洁性。本文以策略模式为例,演示如何利用C++11/14的特性(如std::functionstd::unique_ptrstd::moveauto、范围for等)来实现一种既灵活又安全的策略体系,并结合泛型编程提升可复用性。


1. 策略模式概述

策略模式(Strategy)是一种行为型模式,核心思想是将算法封装在独立的类中,客户端可以在运行时根据需要动态切换策略。典型结构如下:

+-----------------+        +---------------------+
|  Context        | <----> |  Strategy (interface)|
+-----------------+        +---------------------+
       ^                           ^
       |                           |
       |                           |
+-----------------+        +---------------------+
|  ConcreteStrategyA |    | ConcreteStrategyB |
+-----------------+        +---------------------+

在C++中,传统实现往往使用纯虚基类和裸指针来管理策略对象。随着标准的演进,现代C++提供了更安全、更灵活的替代方案。


2. 使用 std::function 代替虚函数表

std::function 能够包装任意可调用对象(函数指针、成员函数、lambda、bind 对象等),并提供统一的调用接口。将策略接口改为:

#include <functional>
#include <string>

class Strategy {
public:
    using Action = std::function<std::string(const std::string&)>;

    // 注册策略
    void set(Action func) { action_ = std::move(func); }

    // 执行
    std::string execute(const std::string& data) const {
        if (!action_) throw std::runtime_error("Strategy not set");
        return action_(data);
    }

private:
    Action action_;
};

优点:

  • 无需继承:消除虚表开销。
  • 灵活切换:运行时可以随意赋值不同的可调用对象。
  • 类型安全:编译器会检查参数/返回值类型。

3. 结合 std::unique_ptr 实现策略生命周期管理

若某些策略需要动态创建且保持唯一所有权,可使用std::unique_ptr。例如:

class Context {
public:
    explicit Context(std::unique_ptr <Strategy> strat)
        : strategy_(std::move(strat)) {}

    void setStrategy(std::unique_ptr <Strategy> strat) {
        strategy_ = std::move(strat);
    }

    std::string process(const std::string& data) const {
        return strategy_->execute(data);
    }

private:
    std::unique_ptr <Strategy> strategy_;
};

这样可以保证:

  • 所有权明确:策略对象只属于Context
  • 防止内存泄漏unique_ptr 自动析构。

4. 示例:文本处理策略

下面给出一个完整示例,展示如何将三种不同文本处理策略(转大写、转小写、替换空格为下划线)组合使用。

#include <iostream>
#include <algorithm>
#include <cctype>
#include <memory>
#include <functional>
#include <string>

// Strategy 与 Context 代码如上

int main() {
    // 创建三种策略
    auto toUpper = std::make_unique <Strategy>();
    toUpper->set([](const std::string& s){
        std::string r = s;
        std::transform(r.begin(), r.end(), r.begin(), [](unsigned char c){ return std::toupper(c); });
        return r;
    });

    auto toLower = std::make_unique <Strategy>();
    toLower->set([](const std::string& s){
        std::string r = s;
        std::transform(r.begin(), r.end(), r.begin(), [](unsigned char c){ return std::tolower(c); });
        return r;
    });

    auto replaceSpace = std::make_unique <Strategy>();
    replaceSpace->set([](const std::string& s){
        std::string r = s;
        std::replace(r.begin(), r.end(), ' ', '_');
        return r;
    });

    // 使用 Context
    Context ctx(std::move(toUpper));
    std::string input = "Hello World! C++ 现代化";

    std::cout << "Original: " << input << "\n";
    std::cout << "Upper:    " << ctx.process(input) << "\n";

    ctx.setStrategy(std::move(toLower));
    std::cout << "Lower:    " << ctx.process(input) << "\n";

    ctx.setStrategy(std::move(replaceSpace));
    std::cout << "Replace:  " << ctx.process(input) << "\n";

    return 0;
}

输出

Original: Hello World! C++ 现代化
Upper:    HELLO WORLD! C++ 现代化
Lower:    hello world! c++ 现代化
Replace:  Hello_World!_C++_现代化

5. 进一步优化:使用模板参数化策略

如果策略不需要在运行时切换,且在编译期已知,可通过模板参数实现更高效的策略。示例:

template<typename Strategy>
class StaticContext {
public:
    StaticContext() : strategy_() {}

    std::string process(const std::string& data) const {
        return strategy_.execute(data);
    }

private:
    Strategy strategy_;
};

使用时:

struct UpperCaseStrategy {
    std::string execute(const std::string& s) const {
        std::string r = s;
        std::transform(r.begin(), r.end(), r.begin(), ::toupper);
        return r;
    }
};

StaticContext <UpperCaseStrategy> ctx;
std::cout << ctx.process("Hello") << "\n";

此时,编译器可以直接内联策略函数,消除运行时的函数调用开销。


6. 结语

通过现代C++11/14的函数对象、智能指针、移动语义与模板技术,策略模式的实现不再需要传统的继承与虚函数,既保持了设计模式的核心思想,又实现了更安全、更灵活、更高效的代码结构。建议在实际项目中,根据是否需要动态切换与编译期优化的权衡,选择适合的实现方式。

发表评论