我知道这个问题很长,但我不确定如何用更短的方式解释我的问题。问题本身是关于类层次结构设计的,特别是如何将基于指针的现有层次结构移植到使用智能指针的层次结构。如果有人能想出一些方法来简化我的解释,从而使这个问题更通用,请告诉我。这样,它可能对更多的 SO 读者有用。
我正在设计一个 C++ 应用程序来处理允许我读取一些传感器的系统。该系统由我收集测量值的远程机器组成。此应用程序实际上必须与两个不同的子系统一起工作:
聚合系统:这种类型的系统包含几个组件,我从中收集测量结果。所有通信都通过聚合系统,如果需要,聚合系统会将数据重定向到特定组件(发送到聚合系统本身的全局命令不需要传输到单个组件)。
独立系统:在这种情况下,只有一个系统,所有通信(包括全局命令)都发送到该系统。
接下来你可以看到我想出的类图:
独立系统继承自ConnMgr
和MeasurementDevice
。另一方面,聚合系统将其功能拆分为AggrSystem
和Component
。
基本上,作为用户,我想要的是一个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;
}
};
这些是类似工厂的函数,负责创建ConnMgr
和MeasurementDevice
对象:
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_cast
in 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_ptr
using this
。我想我可以创建一个额外的间接级别并具有类似的东西StandAloneConnMgr
and StandAloneMeasurementDevice
,其中第一个类将 a 保存shared_ptr
到第二个的实例。
所以,总的来说,我想问一下在使用智能指针时这是否是一种好方法。是否最好使用 a map
ofshared_ptr
并返回 a shared_ptr
,还是基于使用unique_ptr
所有权和原始指针访问的当前方法更好?
PS:create_conn_mgr
并且main
也进行了更改,因此ConnMgr*
我现在使用unique_ptr<ConnMgr>
. 我没有添加代码,因为问题已经足够长了。