4

この質問がかなり長いことは承知していますが、私の問題をより短い方法で説明する方法がわかりませんでした。質問自体は、クラス階層の設計に関するものであり、特に、スマート ポインターを使用するポインターに基づいて既存の階層を移植する方法に関するものです。誰かが私の説明を単純化して、この質問をより一般的なものにする方法を考え出すことができれば、私に知らせてください. そのようにして、より多くの SO リーダーに役立つかもしれません。

一部のセンサーを読み取ることができるシステムを処理するための C++ アプリケーションを設計しています。このシステムは、測定値を収集するリモート マシンで構成されています。このアプリケーションは、実際には 2 つの異なるサブシステムで動作する必要があります。

  1. 集約システム: このタイプのシステムには、測定値を収集するいくつかのコンポーネントが含まれています。すべての通信は、必要に応じてデータを特定のコンポーネントにリダイレクトする集約システムを経由します (集約システム自体に送信されるグローバル コマンドは、個々のコンポーネントに転送する必要はありません)。

  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ため、を返すために生のポインターを返すことにしました。ComponentAggrSystem

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_ptrusing を返すことはできませんthis。間接的なレベルをもう 1 つ作成して、最初のクラスが2 番目のクラスのインスタンスを保持するStandAloneConnMgrandのようなものを作成できると思います。StandAloneMeasurementDeviceshared_ptr

したがって、全体として、スマート ポインターを使用する場合にこれが適切なアプローチであるかどうかを尋ねたいと思いました。mapofを使用してshared_ptra も返す方が望ましいでしょうか、それとも所有権に使用し、アクセスに生のポインタをshared_ptr使用することに基づく現在のアプローチの方がよいでしょうか?unique_ptr

PS:create_conn_mgrmainも同様に変更され、生のポインター ( ConnMgr*)を使用する代わりに を使用するようになりunique_ptr<ConnMgr>ました。質問がすでに十分に長いため、コードを追加しませんでした。

4

1 に答える 1

3

まず、get_measurement_deviceでdynamic_castを回避する方法があるかどうか疑問に思います。

get_measurement_deviceこれを基本クラスの仮想関数にできるように、署名を統合しようと思います。

したがって、全体として、スマートポインターを使用する場合にこれが適切なアプローチであるかどうかを確認したいと思います。

あなたは良い仕事をしたと思います。基本的に、「単一所有権」のニュースを変換unique_ptrし、かなり機械的な方法で削除しました。これはまさに正しい最初の(そしておそらく最後の)ステップです。

get_measurement_deviceまた、元のコードではこの関数のクライアントがこのポインターの所有権を取得していなかったため、生のポインターを返す際に正しい決定を下したと思います。所有権を共有または譲渡する予定がない場合に生のポインターを処理することは、ほとんどのプログラマーが認識する良いパターンです。

要約すると、デザインのセマンティクスを変更せずにスマートポインターを使用するように、既存のデザインを正しく変換しました。

ここから、デザインを共有所有権を含むものに変更する可能性を検討したい場合、それは完全に有効な次のステップです。私自身の好みは、ユースケースまたは状況で共有所有権が要求されるまで、独自の所有権設計を優先することです。

一意の所有権は、より効率的であるだけでなく、推論も容易です。推論が容易になると、通常、偶発的な循環メモリ所有権パターンが少なくなります(循環メモリ所有権==リークされたメモリ)。ポインタを見るたびにshared_ptrをたたくだけのコーダーは、メモリ所有権サイクルで終わる可能性がはるかに高くなります。

そうは言っても、循環メモリの所有権は、のみを使用して可能unique_ptrです。weak_ptrそして、それが発生した場合は、サイクルを中断する必要があり、でweak_ptrのみ機能しshared_ptrます。したがって、所有権サイクルの導入は、に移行するもう1つの理由shared_ptrです。

于 2012-06-14T23:30:50.687 に答える