2012-06-14 55 views
4

我知道這個問題相當長,但我不確定如何以更短的方式解釋我的問題。這個問題本身就是關於類層次結構設計的,特別是如何使用智能指針基於指向一個指針的現有層次結構。如果任何人都可以想出一些方法來簡化我的解釋,並因此使這個問題更通用,請讓我知道。這樣,對於更多的SO讀者可能會有用。將現有的類結構移植到智能指針

我正在設計一個C++應用程序來處理一個允許我讀取一些傳感器的系統。該系統由我從中收集測量數據的遙控機器組成。此應用程序必須實際上有兩個不同的子系統工作:

  1. 彙總系統:這種類型的系統包含了從我收集的測量幾部分組成。所有通信都通過聚合系統,如果需要,數據將重定向到特定組件(發送到聚集系統的全局命令本身不需要傳輸到單個組件)。

  2. 獨立系統:在這種情況下,只有一個系統,並且所有通信(包括全局命令)都被髮送到該系統。

接下來,你可以看到我想出了類圖:

class diagram

的獨立系統繼承了從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_cast方式get_measurement_device。由於AggrSystem::get_measurement_device(int slot)StandAloneSystem::get_measurement_device()具有不同的簽名,因此無法在基類中創建公共虛擬方法。我正在考慮添加一個接受map的通用方法,其中包含選項(例如插槽)。在這種情況下,我不需要進行動態投射。 這是第二種方法更好的清潔設計?

爲了將類層次結構移植到智能指針,我使用了unique_ptr。首先,我改變了組件的mapAggrSystem到:

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; 
} 

在這種情況下,我可以用一個thisshared_ptr返回。我想我可以創建一個額外的間接級別,並且有類似StandAloneConnMgrStandAloneMeasurementDevice,其中第一個類將保留shared_ptr到第二個的實例。

所以,總的來說,我想問一下當使用智能指針時這是否是一個好方法。最好是使用mapshared_ptr並返回shared_ptr,還是更好的方法是基於使用unique_ptr的所有權和原始指針訪問?

P.S:create_conn_mgrmain也進行了更改,以便現在使用unique_ptr<ConnMgr>而不是使用原始指針(ConnMgr*)。由於問題已經足夠長,我沒有添加代碼。

+0

+1上傳照片stackoverlow ;-) – Nawaz

+0

Wowzers。自大學以來,我沒有UML或那麼多解釋。快速點'dynamic_cast'很貴,可能會導致您開始看到的演員陣容。它應該看起來很明顯,但是對於二進制兼容性和單個所有者使用'unique_ptr',並且在可能有多個所有者時使用'shared_ptr'。 – AJG85

+0

@ AJG85我同意'dynamic_cast'。儘管如此,在這種情況下,我並不十分擔心演奏,因爲在初始化時我只會演一次演員。所以,我更關心設計清潔。傳遞一個'map'(與Python使用'kwargs'類似的方式)會更好嗎?我幾乎相信這一點,但聽到別人的意見會很好。 – betabandido

回答

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的另一個很好的理由。

+0

+1非常感謝您的詳細解答! – betabandido