C++设计模式

C++ 中的设计模式是面试高频考点,面试官通常关注模式的核心思想、适用场景、C++ 实现细节(结合多态、内存管理等特性)以及模式间的区别。以下是高频设计模式的面试问题及详细解答:

1. 单例模式(Singleton)

问题:什么是单例模式?请用 C++ 实现线程安全的单例,并说明其优缺点及适用场景。

解答

单例模式是一种创建型模式,确保一个类全局只有一个实例,并提供唯一的全局访问点。

核心思想:
  • 私有构造函数(禁止外部创建实例)。
  • 静态成员变量存储唯一实例。
  • 静态成员函数提供全局访问接口。
线程安全的 C++ 实现(C++11 推荐方案):
cpp 复制代码
class Singleton {
public:
    // 禁用拷贝和移动(防止通过复制创建新实例)
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;

    // 全局访问点:返回局部静态变量的引用(C++11 保证初始化线程安全)
    static Singleton& getInstance() {
        static Singleton instance;  // 仅初始化一次,线程安全
        return instance;
    }

    // 示例功能:单例的业务方法
    void doSomething() { /* ... */ }

private:
    // 私有构造(仅内部可创建)
    Singleton() = default;
    // 私有析构(生命周期与程序一致)
    ~Singleton() = default;
};
优缺点:
  • 优点

    • 严格控制实例数量,节省资源(如全局配置、日志器)。
    • 避免频繁创建销毁实例的开销。
  • 缺点

    • 引入全局状态,增加代码耦合度,不利于单元测试(难以模拟)。
    • 若单例持有资源,可能导致资源释放时机不明确(依赖程序退出)。
适用场景:
  • 全局配置管理器(如读取配置文件的实例,需唯一)。
  • 日志器(避免多实例导致日志文件冲突)。
  • 线程池、连接池(需统一管理资源)。

2. 工厂模式(Factory)

问题:工厂模式有哪几种?请用 C++ 实现工厂方法模式,并说明其与简单工厂、抽象工厂的区别。

解答

工厂模式是创建型模式的总称,核心是将对象创建与使用分离 ,避免直接通过 new 硬编码对象类型。分为三类:

三种工厂模式的区别:
模式 核心思想 优缺点
简单工厂 用一个工厂类根据参数创建不同产品(非设计模式,是一种编程习惯)。 优点:简单直观;缺点:新增产品需修改工厂类,违反 "开闭原则"。
工厂方法 定义抽象工厂基类,每个具体产品对应一个具体工厂子类(通过继承实现扩展)。 优点:符合开闭原则(新增产品只需加新工厂);缺点:类数量爆炸(产品 = 工厂)。
抽象工厂 抽象工厂生产 "产品族"(一组相关产品),具体工厂生产具体产品族。 优点:支持多产品族扩展;缺点:新增单个产品需修改抽象工厂,灵活性较低。
工厂方法模式的 C++ 实现(以日志器为例):

场景:支持两种日志器(文件日志、控制台日志),需灵活扩展新日志类型。

cpp 复制代码
// 1. 抽象产品:日志器基类
class Logger {
public:
    virtual void log(const std::string& msg) = 0;  // 纯虚函数,定义接口
    virtual ~Logger() = default;  // 基类析构需虚函数
};

// 2. 具体产品:文件日志器
class FileLogger : public Logger {
public:
    void log(const std::string& msg) override {
        std::cout << "File log: " << msg << std::endl;
    }
};

// 2. 具体产品:控制台日志器
class ConsoleLogger : public Logger {
public:
    void log(const std::string& msg) override {
        std::cout << "Console log: " << msg << std::endl;
    }
};

// 3. 抽象工厂:日志器工厂基类
class LoggerFactory {
public:
    virtual Logger* createLogger() = 0;  // 纯虚函数,创建产品
    virtual ~LoggerFactory() = default;
};

// 4. 具体工厂:文件日志器工厂
class FileLoggerFactory : public LoggerFactory {
public:
    Logger* createLogger() override {
        return new FileLogger();  // 创建具体产品
    }
};

// 4. 具体工厂:控制台日志器工厂
class ConsoleLoggerFactory : public LoggerFactory {
public:
    Logger* createLogger() override {
        return new ConsoleLogger();  // 创建具体产品
    }
};

// 客户端使用:依赖抽象,不依赖具体
void clientCode(LoggerFactory& factory) {
    Logger* logger = factory.createLogger();
    logger->log("Hello Factory Method");
    delete logger;  // 释放资源(实际可结合智能指针)
}

int main() {
    FileLoggerFactory fileFactory;
    ConsoleLoggerFactory consoleFactory;
    clientCode(fileFactory);      // 输出:File log: ...
    clientCode(consoleFactory);   // 输出:Console log: ...
    return 0;
}
适用场景:
  • 简单工厂:产品类型少且稳定(如简单计算器的运算器)。
  • 工厂方法:产品类型多变,需频繁扩展(如日志器、数据库驱动)。
  • 抽象工厂:需创建一组相关产品(如不同操作系统的 UI 组件族:Windows 按钮 + Windows 文本框)。

3. 观察者模式(Observer)

问题:什么是观察者模式?请用 C++ 实现一个简单的观察者模式(如事件通知),并说明其优缺点。

解答

观察者模式是行为型模式,定义对象间的一对多依赖:当一个对象(主题)状态变化时,所有依赖它的对象(观察者)会自动收到通知并更新。

核心角色:
  • 主题(Subject):维护观察者列表,提供注册、注销和通知接口。
  • 观察者(Observer):定义更新接口,接收主题通知后执行相应操作。
C++ 实现(以 "天气站通知" 为例):

场景:天气站(主题)检测到温度变化时,自动通知所有订阅的显示器(观察者)更新显示。

cpp 复制代码
#include <vector>
#include <memory>  // 智能指针管理内存

// 1. 观察者基类:定义更新接口
class Observer {
public:
    virtual void update(float temperature) = 0;  // 接收温度更新
    virtual ~Observer() = default;
};

// 2. 主题基类:定义注册、注销、通知接口
class Subject {
public:
    virtual void registerObserver(Observer* observer) = 0;
    virtual void removeObserver(Observer* observer) = 0;
    virtual void notifyObservers() = 0;  // 通知所有观察者
    virtual ~Subject() = default;
};

// 3. 具体主题:天气站
class WeatherStation : public Subject {
private:
    float temperature_;  // 状态:温度
    std::vector<Observer*> observers_;  // 观察者列表(弱引用,避免所有权问题)

public:
    void setTemperature(float temp) {
        temperature_ = temp;
        notifyObservers();  // 温度变化,通知观察者
    }

    void registerObserver(Observer* observer) override {
        observers_.push_back(observer);
    }

    void removeObserver(Observer* observer) override {
        // 从列表中移除观察者
        auto it = std::find(observers_.begin(), observers_.end(), observer);
        if (it != observers_.end()) {
            observers_.erase(it);
        }
    }

    void notifyObservers() override {
        // 遍历所有观察者,调用更新方法
        for (Observer* observer : observers_) {
            observer->update(temperature_);
        }
    }
};

// 4. 具体观察者:手机显示器
class PhoneDisplay : public Observer {
public:
    void update(float temperature) override {
        std::cout << "Phone Display: Temperature is " << temperature << "°C" << std::endl;
    }
};

// 4. 具体观察者:电脑显示器
class ComputerDisplay : public Observer {
public:
    void update(float temperature) override {
        std::cout << "Computer Display: Temperature is " << temperature << "°C" << std::endl;
    }
};

// 客户端使用
int main() {
    WeatherStation station;
    PhoneDisplay phone;
    ComputerDisplay computer;

    station.registerObserver(&phone);    // 手机订阅
    station.registerObserver(&computer); // 电脑订阅

    station.setTemperature(25.5f);  // 温度变化,触发通知
    // 输出:
    // Phone Display: Temperature is 25.5°C
    // Computer Display: Temperature is 25.5°C

    station.removeObserver(&phone);     // 手机取消订阅
    station.setTemperature(26.0f);
    // 输出:
    // Computer Display: Temperature is 26.0°C

    return 0;
}
优缺点:
  • 优点

    • 主题与观察者解耦(主题无需知道观察者具体类型)。
    • 支持动态添加 / 删除观察者,灵活性高。
  • 缺点

    • 若观察者过多,通知可能耗时(需考虑异步通知)。
    • 可能导致循环依赖(如观察者同时是主题)。
适用场景:
  • 事件驱动系统(如 GUI 按钮点击事件:按钮是主题,回调函数是观察者)。
  • 发布 - 订阅模式(如消息队列:生产者是主题,消费者是观察者)。
  • 状态监控(如股票价格更新、传感器数据通知)。

4. 装饰器模式(Decorator)

问题:装饰器模式的作用是什么?请用 C++ 实现一个装饰器模式(如给咖啡加配料),并说明其与继承的区别。

解答

装饰器模式是结构型模式,动态地给对象添加额外功能,同时不改变其原始结构。通过 "组合" 而非 "继承" 实现功能扩展,避免类爆炸。

与继承的区别:
  • 继承:编译时静态扩展,子类耦合父类,功能固定且类数量随功能组合呈指数增长(如 "咖啡 + 奶 + 糖" 需单独子类)。
  • 装饰器:运行时动态扩展,通过组合叠加功能,类数量线性增长(每个功能一个装饰器)。
C++ 实现(咖啡加配料场景):

场景:基础咖啡(美式)可动态添加奶、糖、巧克力等配料,每种配料增加价格和描述。

cpp 复制代码
#include <string>
#include <iostream>

// 1. 抽象组件:咖啡基类
class Coffee {
public:
    virtual std::string getDescription() = 0;  // 描述
    virtual double getPrice() = 0;             // 价格
    virtual ~Coffee() = default;
};

// 2. 具体组件:基础咖啡(美式)
class Americano : public Coffee {
public:
    std::string getDescription() override {
        return "Americano";
    }
    double getPrice() override {
        return 10.0;
    }
};

// 3. 装饰器基类:继承咖啡接口,持有咖啡对象(组合)
class CoffeeDecorator : public Coffee {
protected:
    Coffee* coffee_;  // 被装饰的咖啡(指针避免对象切片)
public:
    explicit CoffeeDecorator(Coffee* coffee) : coffee_(coffee) {}
    ~CoffeeDecorator() override { delete coffee_; }  // 释放被装饰对象
};

// 4. 具体装饰器:加奶
class MilkDecorator : public CoffeeDecorator {
public:
    explicit MilkDecorator(Coffee* coffee) : CoffeeDecorator(coffee) {}
    std::string getDescription() override {
        return coffee_->getDescription() + ", Milk";
    }
    double getPrice() override {
        return coffee_->getPrice() + 2.0;  // 奶加2元
    }
};

// 4. 具体装饰器:加糖
class SugarDecorator : public CoffeeDecorator {
public:
    explicit SugarDecorator(Coffee* coffee) : CoffeeDecorator(coffee) {}
    std::string getDescription() override {
        return coffee_->getDescription() + ", Sugar";
    }
    double getPrice() override {
        return coffee_->getPrice() + 1.0;  // 糖加1元
    }
};

// 客户端使用:动态组合功能
int main() {
    // 美式 + 奶 + 糖
    Coffee* coffee = new SugarDecorator(
        new MilkDecorator(
            new Americano()
        )
    );

    std::cout << "Description: " << coffee->getDescription() << std::endl;  // Americano, Milk, Sugar
    std::cout << "Price: " << coffee->getPrice() << std::endl;              // 13.0

    delete coffee;  // 装饰器链会递归释放所有对象
    return 0;
}
适用场景:
  • 需动态扩展对象功能(如 IO 流:std::ifstream 可被 std::buffered_stream 装饰以增加缓冲功能)。
  • 功能组合多样(如咖啡配料、GUI 组件的边框 / 滚动条组合)。

5. 适配器模式(Adapter)

问题:什么是适配器模式?请用 C++ 实现类适配器和对象适配器,并说明其区别。

解答

适配器模式是结构型模式,将一个类的接口转换为客户端期望的另一个接口,使原本因接口不兼容而无法协作的类可以一起工作。

两种适配器的区别:
类型 实现方式 优缺点
类适配器 继承被适配类(Adaptee)和目标接口(Target),通过多继承实现。 优点:直接复用被适配类的行为;缺点:依赖多继承,灵活性低(C++ 支持,Java 不支持)。
对象适配器 持有被适配类的实例(组合),实现目标接口。 优点:更灵活(可适配任意被适配类的子类);缺点:需转发所有接口调用。
C++ 实现(适配旧接口到新接口):

场景 :已有一个旧的日志类 OldLogger(接口为 logMessage),需适配到新接口 NewLogger(接口为 log)。

cpp 复制代码
#include <string>
#include <iostream>

// 1. 目标接口:客户端期望的新接口
class NewLogger {
public:
    virtual void log(const std::string& msg) = 0;  // 新接口:log
    virtual ~NewLogger() = default;
};

// 2. 被适配类:旧接口
class OldLogger {
public:
    // 旧接口:logMessage(参数和名称与新接口不同)
    void logMessage(const char* msg, int level) {
        std::cout << "OldLogger [level " << level << "]: " << msg << std::endl;
    }
};

// 3. 类适配器:继承旧类和新接口(多继承)
class ClassAdapter : public NewLogger, private OldLogger {
public:
    void log(const std::string& msg) override {
        // 适配:将新接口调用转换为旧接口
        logMessage(msg.c_str(), 1);  // 固定level=1
    }
};

// 4. 对象适配器:持有旧类实例(组合)
class ObjectAdapter : public NewLogger {
private:
    OldLogger* oldLogger_;  // 持有被适配对象
public:
    explicit ObjectAdapter(OldLogger* logger) : oldLogger_(logger) {}
    void log(const std::string& msg) override {
        // 适配:转发到旧接口
        oldLogger_->logMessage(msg.c_str(), 1);
    }
};

// 客户端:只依赖新接口
void client(NewLogger& logger) {
    logger.log("Hello Adapter");
}

int main() {
    ClassAdapter classAdapter;
    client(classAdapter);  // 输出:OldLogger [level 1]: Hello Adapter

    OldLogger oldLogger;
    ObjectAdapter objectAdapter(&oldLogger);
    client(objectAdapter);  // 输出:OldLogger [level 1]: Hello Adapter

    return 0;
}
适用场景:
  • 集成 legacy 代码(旧系统接口适配新系统)。
  • 复用第三方库(库接口与本地接口不兼容时)。

总结

C++ 设计模式面试重点关注:

  • 创建型模式(单例、工厂):解决对象创建问题,结合 C++ 构造函数、静态成员、智能指针。
  • 结构型模式(装饰器、适配器):解决类 / 对象组合问题,体现 "组合优于继承" 原则。
  • 行为型模式(观察者):解决对象间交互问题,依赖 C++ 多态(虚函数)实现解耦。

回答时需结合具体场景说明模式的 "为什么用""怎么用",并对比相似模式的区别(如工厂方法 vs 抽象工厂,装饰器 vs 适配器)。

相关推荐
软行4 小时前
LeetCode 每日一题 166. 分数到小数
数据结构·c++·算法·leetcode·哈希算法
SunkingYang4 小时前
C++变量与函数命名规范技术指南 (基于华为编码规范与现代C++最佳实践)
c++·华为·编码规范·命名规则·命名规范·函数名字·成员变量
夜晚中的人海4 小时前
【C++】二分查找算法习题
开发语言·c++·算法
sulikey5 小时前
【C++ STL 深入解析】insert 与 emplace 的区别与联系(以 multimap 为例)
开发语言·c++·stl·stl容器·insert·emplace
fqbqrr5 小时前
2510C++,rest_rpc
c++·rpc
R-G-B5 小时前
【23】MFC入门到精通——MFC资源视图 报错“在另一个编辑器中打开” ,MFC Dialog窗口消失 资源视图“在另一个编译器中打开”
c++·编辑器·mfc·“在另一个编辑器中打开”·mfc dialog窗口消失
闻缺陷则喜何志丹5 小时前
【单调队列 多重背包】P1776 宝物筛选|普及+
c++·算法·动态规划·洛谷·多重背包·单调队列
Coolbike6 小时前
《深度探索C++对象模型》笔记
c++·笔记