我知道這個問題相當長,但我不確定如何以更短的方式解釋我的問題。這個問題本身就是關於類層次結構設計的,特別是如何使用智能指針基於指向一個指針的現有層次結構。如果任何人都可以想出一些方法來簡化我的解釋,並因此使這個問題更通用,請讓我知道。這樣,對於更多的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;
}
在這種情況下,我可以用一個this
不shared_ptr
返回。我想我可以創建一個額外的間接級別,並且有類似StandAloneConnMgr
和StandAloneMeasurementDevice
,其中第一個類將保留shared_ptr
到第二個的實例。
所以,總的來說,我想問一下當使用智能指針時這是否是一個好方法。最好是使用map
的shared_ptr
並返回shared_ptr
,還是更好的方法是基於使用unique_ptr
的所有權和原始指針訪問?
P.S:create_conn_mgr
和main
也進行了更改,以便現在使用unique_ptr<ConnMgr>
而不是使用原始指針(ConnMgr*
)。由於問題已經足夠長,我沒有添加代碼。
+1上傳照片stackoverlow ;-) – Nawaz
Wowzers。自大學以來,我沒有UML或那麼多解釋。快速點'dynamic_cast'很貴,可能會導致您開始看到的演員陣容。它應該看起來很明顯,但是對於二進制兼容性和單個所有者使用'unique_ptr',並且在可能有多個所有者時使用'shared_ptr'。 – AJG85
@ AJG85我同意'dynamic_cast'。儘管如此,在這種情況下,我並不十分擔心演奏,因爲在初始化時我只會演一次演員。所以,我更關心設計清潔。傳遞一個'map'(與Python使用'kwargs'類似的方式)會更好嗎?我幾乎相信這一點,但聽到別人的意見會很好。 – betabandido