0

2 つのクライアントが ZeroMQ PUB/SUB ソケットを使用して通信できるようにするライブラリを作成しています。各クライアント アプリケーションは、ブロードキャスター エンドポイントまたはレシーバー エンドポイントのいずれかをインスタンス化し、これらのエンドポイント クラスには Connection メンバーがあります。

class Connection {
    Connection(const char* address, int outgoingPort, int incomingPort);
};

接続にはいくつかのソケットがあり、それぞれのポートを介して指定されたアドレスに接続するように構成されています。ただし、接続が実際にインスタンス化されるクラスでこれらの詳細を公開することは気にしません。ベース接続オブジェクトには着信ポートと発信ポートがありますが、この詳細はプログラムの残りの部分に浸透する必要はありません。これらの上位層では、データ ポートと制御ポートという 2 つの指定ポートの観点から考える方が賢明です。そのため、特定のタイプの接続の着信ポートと発信ポートを定義するコンストラクターを実装する 2 つのサブクラスがあります。

class BroadcasterConnection : public Connection {
    BroadcasterConnection(int dataPort, int controlPort)
    :Connection("*", dataPort, controlPort) {}
};
class ReceiverConnection : public Connection {
    ReceiverConnection(const char* hostAddress, int dataPort, int controlPort)
    :Connection(hostAddress, controlPort, dataPort) {}
};

"*"さらに、ブロードキャスターは安定したエンドポイントとしてポートにバインドするため、実際のリモート アドレスの代わりに使用する必要があります。繰り返しになりますが、ブロードキャスター接続をインスタンス化して使用するクラスは、この詳細に関与する必要がないため、BroadcasterConnection コンストラクターが処理します。

別の例として、ZeroMQ ソケットをラップするクラスで同じことを行います。ベース Socket クラスがあり、サブクラス コンストラクターは適切な値 (ZMQ_PUB または ZMQ_SUB) を ZeroMQ ヘッダーから基になるソケットに渡すだけです。クライアントが ZeroMQ の値を直接使用することはできないため、正式な方法で PUB ソケットと SUB ソケットの区別を成文化する必要があり、単一のサブクラス コンストラクターを提供することは、透過的で賢明な方法のように思えました。

class Socket:
    Socket(void* context, const char* address, int port, int socketType);

class PublishSocket : public Socket:
    PublishSocket(void* context, const char* address, int port)
    :Socket(context, address, port, ZMQ_PUB) {}

class SubscribeSocket : public Socket:
    SubscribeSocket(void* context, const char* address, int port)
    :Socket(context, address, port, ZMQ_SUB) {}

これらのサブクラスは、特別なことは何もしませんが、抽象化のサービスにおいて有用で健全な追加であることに同意していただければ幸いです。しかし、私はこの単純な慣用句の一般的な名前を知りません。コンストラクターのみを実装するサブクラスを定義するとき、より特殊化されたパラメーターのセットを使用してオブジェクトを構築するためだけに、私は何をしていますか?

ここで重要な点は、これらのサブクラスが追加のメソッドやデータを定義していないということです。別の例を次に示します。ここには、任意のタイプの任意のエンティティを識別する基本 Tag クラスがあります。サブクラスは、いくつかのドメイン固有のパラメーターに基づいてエンティティの個々のタイプのタグを作成するために使用されますが、最終的にはすべて Tag オブジェクトになります。

Tag(char typeIdentifier, int entityIdentifier);

LightTag(int lightIndex):Tag('L', lightIndex) {}
SkeletonTag(const char* skeletonName):Tag('S', hash(skeletonName)) {}
CameraTag():Tag('C', 0) {}

それで、いくつか質問があります:

  1. このイディオムに一般的に使用されている Google 対応の名前はありますか?

  2. と書くConnection c = BroadcasterConnection(40001, 40002);と、コピーコンストラクターが呼び出されます。は追加のデータを定義していないためBroadcasterConnection、2 つのクラスは互換性があり (RTTI に関係なく)、オブジェクトのスライスを心配することなくダウンキャストできるはずですよね? コピーを回避するこの方法でオブジェクトを構築するための同様に便利な構文はありますか? これは、コンストラクターの初期化リストでも発生するようです。

  3. Connection* c = new BroadcasterConnection(40001, 40002);これはあまり実用的な例ではありませんが、次にと書いたとしdelete c;ます。Connection には仮想デストラクタがありませんが、そもそも仮想関数がありません (したがって、vtable はありません)。BroadcasterConnection は、追加データを定義しない Connection の直接のサブクラスであるため、この操作は安全でしょうか? BroadcasterConnection がメンバー データを追加した場合はどうなるでしょうか。その後、メモリリークが発生しますか?

  4. 特定のサブクラスが上記の方法でコンストラクターのみであるという事実を明示的に体系化して、コンパイラーが追加のデータを含めることを許可しないようにする方法はありますか?

そしてもちろん、同じ問題を根本的に解決するためのより良い方法があれば、ぜひ聞きたいです。

4

1 に答える 1

3

気をつけて。未定義の動作を呼び出しました。

5.3.5

3) 最初の選択肢 (delete object ) で、オペランドの静的型がその動的型と異なる場合、静的型はオペランドの動的型の基本クラスであり、静的型は仮想デストラクタまたは動作を持たなければならない未定義です。[...]

ほとんどの場合はうまくいくと思いますが、未定義の動作を呼び出しているため、ハード ドライブの内容をフォト プリンターに電子メールで送信し、クレジット カードを使用して支払いを行うようにコンパイラーに依頼しました。またはそれがどのように感じるかは何でも。

実際には、おそらく基本クラスのデストラクタを呼び出すだけです。

上記のユーティリティのほとんどは、派生クラスの名前を持つ基本クラス オブジェクトのコピーを返す関数を介して処理できることに注意してください。SubscribeSocketつまり、SubscribeSocket というクラスの代わりに、 Socket. 移動のセマンティクス ( の高速移動がありますSocketよね?) と RVO ( の実装を公開する意思がある場合SubscribeSocket) の間では、これは効率的です。

Socketスキームの利点の 1 つは、必要に応じて sを入力できることです。未定義の動作を呼び出さない (ただし、いくつかの癖があります) 動作するアプローチは、とはSubscribeSocket無関係のクラスを定義することです。を必要とする API 。明示的に必要な場合にメソッドを投入します。回避すると、スライスがブロックされます。デバッグでチェックを行う明示的な「ソケットから作成」タイプの関数があるかもしれません...SocketSocketoperator Socket&()operator Socket const&() constSockets.GetSocket()operator=(Socket const&)

于 2013-03-07T20:42:36.937 に答える