この質問がかなり長いことは承知していますが、私の問題をより短い方法で説明する方法がわかりませんでした。質問自体は、クラス階層の設計に関するものであり、特に、スマート ポインターを使用するポインターに基づいて既存の階層を移植する方法に関するものです。誰かが私の説明を単純化して、この質問をより一般的なものにする方法を考え出すことができれば、私に知らせてください. そのようにして、より多くの SO リーダーに役立つかもしれません。
一部のセンサーを読み取ることができるシステムを処理するための C++ アプリケーションを設計しています。このシステムは、測定値を収集するリモート マシンで構成されています。このアプリケーションは、実際には 2 つの異なるサブシステムで動作する必要があります。
集約システム: このタイプのシステムには、測定値を収集するいくつかのコンポーネントが含まれています。すべての通信は、必要に応じてデータを特定のコンポーネントにリダイレクトする集約システムを経由します (集約システム自体に送信されるグローバル コマンドは、個々のコンポーネントに転送する必要はありません)。
スタンドアロン システム: この場合、システムは 1 つだけであり、すべての通信 (グローバル コマンドを含む) はそのシステムに送信されます。
次に、私が思いついたクラス図を見ることができます:
スタンドアロン システムは、 と の両方を継承しConnMgr
ますMeasurementDevice
。一方、集約システムは、その機能を と の間AggrSystem
で分割しますComponent
。
基本的に、ユーザーとして私が持ちたいのはMeasurementDevice
オブジェクトであり、集約システムであろうとスタンドアロンシステムであろうと、対応するエンドポイントにデータを透過的に送信します。
現在の実装
これが私の現在の実装です。まず、2 つの基本抽象クラス:
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
オプション (例: スロット) を受け入れる一般的なメソッドを追加することを考えていました。その場合、動的キャストを行う必要はありません。この 2 番目のアプローチは、よりクリーンなデザインという点で望ましいのでしょうか?
クラス階層をスマート ポインターに移植するために、unique_ptr
. まずmap
、コンポーネントのを次のように変更しましAggrSystem
た。
std::map<int, std::unique_ptr<Component> > components_;
new の追加は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
using を返すことはできませんthis
。間接的なレベルをもう 1 つ作成して、最初のクラスが2 番目のクラスのインスタンスを保持するStandAloneConnMgr
andのようなものを作成できると思います。StandAloneMeasurementDevice
shared_ptr
したがって、全体として、スマート ポインターを使用する場合にこれが適切なアプローチであるかどうかを尋ねたいと思いました。map
ofを使用してshared_ptr
a も返す方が望ましいでしょうか、それとも所有権に使用し、アクセスに生のポインタをshared_ptr
使用することに基づく現在のアプローチの方がよいでしょうか?unique_ptr
PS:create_conn_mgr
とmain
も同様に変更され、生のポインター ( ConnMgr*
)を使用する代わりに を使用するようになりunique_ptr<ConnMgr>
ました。質問がすでに十分に長いため、コードを追加しませんでした。