我知道这个问题很长,但我不确定如何以更短的方式解释我的问题。问题本身是关于类层次结构设计,尤其是如何基于使用智能指针的指针移植现有层次结构。如果有人能想出一些方法来简化我的解释,那么,让这个问题更通用,请告诉我。通过这种方式,它可能对更多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
中的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))));
}
为了返回Component
,我决定返回一个原始指针,因为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();
}
返回原始指针是否正确?如果我使用shared_ptr
,那么我遇到了独立系统实现的问题:
MeasurementDevice* StandAloneSystem::get_measurement_device() {
return this;
}
在这种情况下,我无法使用shared_ptr
返回this
。我想我可以创建一个额外的间接级别,并且有类似StandAloneConnMgr
和StandAloneMeasurementDevice
的内容,其中第一个类将shared_ptr
保存到第二个类的实例。
所以,总的来说,我想问一下使用智能指针时这是否是一个好方法。最好使用map
shared_ptr
并返回shared_ptr
,还是基于使用unique_ptr
进行所有权和原始指针访问的当前方法更好?
P.S:create_conn_mgr
和main
也被更改,因此我现在使用ConnMgr*
而不是使用原始指针(unique_ptr<ConnMgr>
)。我没有添加代码,因为问题已经足够长了。
答案 0 :(得分: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
的另一个好理由。