4

我知道这个问题很长,但我不确定如何用更短的方式解释我的问题。问题本身是关于类层次结构设计的,特别是如何将基于指针的现有层次结构移植到使用智能指针的层次结构。如果有人能想出一些方法来简化我的解释,从而使这个问题更通用,请告诉我。这样,它可能对更多的 SO 读者有用。

我正在设计一个 C++ 应用程序来处理允许我读取一些传感器的系统。该系统由我收集测量值的远程机器组成。此应用程序实际上必须与两个不同的子系统一起工作:

  1. 聚合系统:这种类型的系统包含几个组件,我从中收集测量结果。所有通信都通过聚合系统,如果需要,聚合系统会将数据重定向到特定组件(发送到聚合系统本身的全局命令不需要传输到单个组件)。

  2. 独立系统:在这种情况下,只有一个系统,所有通信(包括全局命令)都发送到该系统。

接下来你可以看到我想出的类图:

类图

独立系统继承自ConnMgrMeasurementDevice。另一方面,聚合系统将其功能拆分为AggrSystemComponent

基本上,作为用户,我想要的是一个MeasurementDevice对象并将数据透明地发送到相应的端点,无论是聚合系统还是独立系统。

当前实施

这是我目前的实现。首先,两个基本抽象类:

class MeasurementDevice {
public:
    virtual ~MeasurementDevice() {}
    virtual void send_data(const std::vector<char>& data) = 0;
};

class ConnMgr {
public:
    ConnMgr(const std::string& addr) : addr_(addr) {}
    virtual ~ConnMgr() {}
    virtual void connect() = 0;
    virtual void disconnect() = 0;

protected:
    std::string addr_;
};

这些是聚合系统的类:

class Component : public MeasurementDevice {
public:
    Component(AggrSystem& as, int slot) : aggr_sys_(as), slot_(slot) {}
    void send_data(const std::vector<char>& data) {
        aggr_sys_.send_data(slot_, data);
    }
private:
    AggrSystem& aggr_sys_;
    int slot_;
};

class AggrSystem : public ConnMgr {
public:
    AggrSystem(const std::string& addr) : ConnMgr(addr) {}
    ~AggrSystem() { for (auto& entry : components_) delete entry.second; }

    // overridden virtual functions omitted (not using smart pointers)

    MeasurementDevice* get_measurement_device(int slot) {
        if (!is_slot_used(slot)) throw std::runtime_error("Empty slot");
        return components_.find(slot)->second;
    }
private:
    std::map<int, Component*> components_;

    bool is_slot_used(int slot) const {
        return components_.find(slot) != components_.end();
    }
    void add_component(int slot) {
        if (is_slot_used(slot)) throw std::runtime_error("Slot already used");
        components_.insert(std::make_pair(slot, new Component(*this, slot)));
    }
};

这是独立系统的代码:

class StandAloneSystem : public ConnMgr, public MeasurementDevice {
public:
    StandAloneSystem(const std::string& addr) : ConnMgr(addr) {}

    // overridden virtual functions omitted (not using smart pointers)

    MeasurementDevice* get_measurement_device() {
        return this;
    }
};

这些是类似工厂的函数,负责创建ConnMgrMeasurementDevice对象:

typedef std::map<std::string, boost::any> Config;

ConnMgr* create_conn_mgr(const Config& cfg) {
    const std::string& type =
        boost::any_cast<std::string>(cfg.find("type")->second);
    const std::string& addr =
        boost::any_cast<std::string>(cfg.find("addr")->second);

    ConnMgr* ep;
    if (type == "aggregated") ep = new AggrSystem(addr);
    else if (type == "standalone") ep = new StandAloneSystem(addr);
    else throw std::runtime_error("Unknown type");
    return ep;
}

MeasurementDevice* get_measurement_device(ConnMgr* ep, const Config& cfg) {
    const std::string& type =
        boost::any_cast<std::string>(cfg.find("type")->second);

    if (type == "aggregated") {
        int slot = boost::any_cast<int>(cfg.find("slot")->second);
        AggrSystem* aggr_sys = dynamic_cast<AggrSystem*>(ep);
        return aggr_sys->get_measurement_device(slot);
    }
    else if (type == "standalone") return dynamic_cast<StandAloneSystem*>(ep);
    else throw std::runtime_error("Unknown type");
}

最后是这里main(),展示了一个非常简单的用例:

#define USE_AGGR

int main() {
    Config config = {
        { "addr", boost::any(std::string("192.168.1.10")) },
#ifdef USE_AGGR
        { "type", boost::any(std::string("aggregated")) },
        { "slot", boost::any(1) },
#else
        { "type", boost::any(std::string("standalone")) },
#endif
    };

    ConnMgr* ep = create_conn_mgr(config);
    ep->connect();

    MeasurementDevice* dev = get_measurement_device(ep, config);
    std::vector<char> data; // in real life data should contain something
    dev->send_data(data);

    ep->disconnect();
    delete ep;
    return 0;
}

拟议变更

首先,我想知道是否有办法避免dynamic_castin get_measurement_device。由于AggrSystem::get_measurement_device(int slot)StandAloneSystem::get_measurement_device()具有不同的签名,因此不可能在基类中创建通用的虚拟方法。我正在考虑添加一个接受map包含选项的通用方法(例如,插槽)。在这种情况下,我不需要进行动态转换。就更清洁的设计而言,第二种方法是否更可取?

为了将类层次结构移植到我使用的智能指针unique_ptr。首先,我将map组件更改AggrSystem为:

std::map<int, std::unique_ptr<Component> > components_;

添加一个新的Component现在看起来像:

void AggrSystem::add_component(int slot) {
    if (is_slot_used(slot)) throw std::runtime_error("Slot already used");
    components_.insert(std::make_pair(slot,
        std::unique_ptr<Component>(new Component(*this, slot))));
}

为了返回 aComponent我决定返回一个原始指针,因为对象的生命周期Component是由对象的生命周期定义的AggrSystem

MeasurementDevice* AggrSystem::get_measurement_device(int slot) {
    if (!is_slot_used(slot)) throw std::runtime_error("Empty slot");
    return components_.find(slot)->second.get();
}

返回原始指针是正确的决定吗?但是,如果我使用 a shared_ptr,那么我会在独立系统的实现中遇到问题:

MeasurementDevice* StandAloneSystem::get_measurement_device() {
    return this;
}

在这种情况下,我无法返回shared_ptrusing this。我想我可以创建一个额外的间接级别并具有类似的东西StandAloneConnMgrand StandAloneMeasurementDevice,其中第一个类将 a 保存shared_ptr到第二个的实例。

所以,总的来说,我想问一下在使用智能指针时这是否是一种好方法。是否最好使用 a mapofshared_ptr并返回 a shared_ptr,还是基于使用unique_ptr所有权和原始指针访问的当前方法更好?

PS:create_conn_mgr并且main也进行了更改,因此ConnMgr*我现在使用unique_ptr<ConnMgr>. 我没有添加代码,因为问题已经足够长了。

4

1 回答 1

3

首先,我想知道是否有办法避免get_measurement_device中的dynamic_cast。

我会尝试统一get_measurement_device签名,以便您可以将其作为基类中的虚拟函数。

所以,总的来说,我想问一下在使用智能指针时这是否是一种好方法。

我认为你做得很好。您基本上已经将您的“单一所有权”新闻和删除转换unique_ptr为相当机械的方式。这正是正确的第一步(也许是最后一步)。

我还认为您在返回原始指针方面做出了正确的决定,get_measurement_device因为在您的原始代码中,该函数的客户端没有获得该指针的所有权。当您不打算共享或转让所有权时处理原始指针是大多数程序员都会认可的一种很好的模式。

总之,您已经正确地将现有设计转换为使用智能指针,而无需更改设计的语义。

从这里开始,如果您想研究将设计更改为涉及共享所有权的设计的可能性,那么这是一个非常有效的下一步。我自己的偏好是更喜欢独特的所有权设计,直到用例或情况需要共享所有权。

唯一所有权不仅效率更高,而且更容易推理。这种易于推理通常会导致更少的意外循环内存所有权模式(循环内存所有权==泄漏内存)。每次看到指针时就敲击 shared_ptr 的编码人员更有可能以内存所有权周期告终。

话虽如此,循环内存所有权也可以仅使用unique_ptr. 如果发生这种情况,您需要weak_ptr打破循环,并且weak_ptr仅适用于shared_ptr. 因此,引入所有权周期是迁移到shared_ptr.

于 2012-06-14T23:30:50.687 回答