MST

星途 面试题库

面试题:C++ 类与对象代码复用的高级应用及内存管理

假设你正在开发一个图形绘制库,有一个基类`Shape`,包含纯虚函数`void draw()`用于绘制图形。有派生类`Circle`和`Rectangle`继承自`Shape`,各自实现`draw`函数。现在要求设计一个`ShapeContainer`类,通过代码复用技术(可使用模板、继承、组合等)来管理不同类型的`Shape`对象,并且要考虑高效的内存管理(如智能指针的合理使用)。在`ShapeContainer`类中实现添加`Shape`对象、删除特定`Shape`对象以及遍历绘制所有`Shape`对象的功能。请写出完整的设计思路及核心代码,并详细解释在这个场景下如何避免内存泄漏以及不同代码复用方式对整体架构的影响。
25.1万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 内存管理:使用智能指针来管理Shape对象,以避免手动内存管理带来的内存泄漏风险。std::unique_ptr 适合独占所有权的场景,而 std::shared_ptr 适用于共享所有权的情况。这里由于ShapeContainer将管理Shape对象的生命周期,使用std::unique_ptr即可。
  2. 代码复用方式:选择组合方式,在ShapeContainer类中包含一个容器(如std::vector)来存储Shape对象的智能指针。这种方式相比于继承更加灵活,因为继承是一种“是一种”的关系,而ShapeContainer并不是一种Shape,而是“包含”多种Shape,组合更符合这种关系。
  3. 功能实现
    • 添加Shape对象:在ShapeContainer类中提供一个函数,接受std::unique_ptr<Shape>参数,并将其添加到容器中。
    • 删除特定Shape对象:提供一个函数,通过某种标识(如索引或特定条件)找到要删除的Shape对象,并从容器中移除。
    • 遍历绘制所有Shape对象:遍历容器,调用每个Shape对象的draw函数。

核心代码

#include <iostream>
#include <memory>
#include <vector>

// 基类Shape
class Shape {
public:
    virtual void draw() const = 0;
    virtual ~Shape() = default;
};

// 派生类Circle
class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing a Circle" << std::endl;
    }
};

// 派生类Rectangle
class Rectangle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing a Rectangle" << std::endl;
    }
};

// ShapeContainer类
class ShapeContainer {
private:
    std::vector<std::unique_ptr<Shape>> shapes;

public:
    // 添加Shape对象
    void addShape(std::unique_ptr<Shape> shape) {
        shapes.emplace_back(std::move(shape));
    }

    // 删除特定Shape对象(通过索引)
    void removeShape(size_t index) {
        if (index < shapes.size()) {
            shapes.erase(shapes.begin() + index);
        }
    }

    // 遍历绘制所有Shape对象
    void drawAll() const {
        for (const auto& shape : shapes) {
            shape->draw();
        }
    }
};

避免内存泄漏

  1. 智能指针的使用:使用std::unique_ptr来管理Shape对象,std::unique_ptr在其析构时会自动释放所指向的对象。当ShapeContainer对象被销毁时,其包含的std::vector会销毁其中所有的std::unique_ptr,进而释放对应的Shape对象,避免了手动释放内存带来的遗漏和错误。
  2. 正确的资源管理流程:在添加Shape对象时,使用std::movestd::unique_ptr的所有权转移到ShapeContainerstd::vector中,确保资源的正确转移和唯一所有权。在删除Shape对象时,std::vectorerase操作会自动调用std::unique_ptr的析构函数,释放相应的Shape对象。

不同代码复用方式对整体架构的影响

  1. 继承:如果使用继承,ShapeContainer将从Shape继承,这意味着ShapeContainer也是一种Shape,这与实际需求不符。此外,继承会导致ShapeContainer拥有Shape的所有接口和状态,增加了不必要的复杂性,并且在管理多种Shape对象时不够灵活。例如,如果需要添加新的Shape类型,继承方式需要在ShapeContainer类中添加更多的处理逻辑,违反了开闭原则。
  2. 组合:通过组合方式,ShapeContainer只需要关心如何管理Shape对象,而不需要继承Shape的接口和状态。这种方式更加灵活,易于扩展。当有新的Shape类型添加时,只需要在外部创建该Shape类型的std::unique_ptr并添加到ShapeContainer中,ShapeContainer本身不需要修改,符合开闭原则。同时,组合方式使得ShapeContainerShape之间的耦合度降低,提高了代码的可维护性和可复用性。