13

GoogleのJava用ProtocolBufferAPIでは、オブジェクトを作成する次の優れたビルダーを使用しています(ここを参照)。

Person john =
  Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .addPhone(
      Person.PhoneNumber.newBuilder()
        .setNumber("555-4321")
        .setType(Person.PhoneType.HOME))
    .build();

ただし、対応するC ++ APIはそのようなビルダーを使用しません(ここを参照)

C++とJavaAPIは同じことをしているはずなので、なぜC++でもビルダーを使用しなかったのか疑問に思います。その背後にある言語上の理由がありますか?つまり、それは慣用的ではないか、C ++で嫌われていますか?それとも、C++バージョンのProtocolBuffersを書いた人の個人的な好みですか?

4

6 に答える 6

8

C ++でそのようなものを実装する適切な方法は、*thisへの参照を返すセッターを使用することです。

class Person {
  std::string name;
public:
  Person &setName(string const &s) { name = s; return *this; }
  Person &addPhone(PhoneNumber const &n);
};

同様に定義されたPhoneNumberを想定すると、クラスは次のように使用できます。

Person p = Person()
  .setName("foo")
  .addPhone(PhoneNumber()
    .setNumber("123-4567"));

別のビルダークラスが必要な場合は、それも実行できます。もちろん、そのようなビルダーはスタックに割り当てる必要があります。

于 2010-02-19T07:58:29.060 に答える
4

C++ コードでこのような流暢なインターフェイス スタイルの例を見たことがありますが、私は「慣用的ではない」と考えています。

同じ根本的な問題に取り組む方法がいくつかあるためかもしれません。通常、ここで解決される問題は、名前付き引数の問題 (というより、名前付き引数の欠如) です。おそらく、この問題に対するよりC++ に似た解決策は、 Boost の Parameter libraryです。

于 2010-02-19T08:18:20.970 に答える
2

この違いは部分的に慣用的なものですが、C++ ライブラリがより高度に最適化された結果でもあります。

あなたの質問で注意しなかったことの 1 つは、protoc によって発行された Java クラスは不変であるため、(潜在的に) 非常に長い引数リストを持ち、setter メソッドを持たないコンストラクターが必要であるということです。不変パターンは Java で一般的に使用され、(パフォーマンスを犠牲にして) マルチスレッドに関連する複雑さを回避します。ビルダー パターンは、大規模なコンストラクター呼び出しで目を細めて、すべての値を同時に使用できるようにする必要があるという苦痛を回避するために使用されます。コードでポイントします。

protoc によって出力される C++ クラスは不変ではなく、複数のメッセージ受信でオブジェクトを再利用できるように設計されています ( C++ の基本ページの「最適化のヒント」セクションを参照してください)。したがって、使用するのは難しく危険ですが、より効率的です。

確かに、2 つの実装が同じスタイルで記述された可能性はありますが、開発者は、Java では使いやすさがより重要であり、C++ ではパフォーマンスがより重要であると感じているようでした。おそらく、これらの言語の使用パターンをグーグル。

于 2011-02-04T17:54:01.573 に答える
1

私のコメントをフォローアップするには...

struct Person
{
   int id;
   std::string name;

   struct Builder
   {
      int id;
      std::string name;
      Builder &setId(int id_)
      {
         id = id_;
         return *this;
      }
      Builder &setName(std::string name_)
      {
         name = name_;
         return *this;
      }
   };

   static Builder build(/* insert mandatory values here */)
   {
      return Builder(/* and then use mandatory values here */)/* or here: .setId(val) */;
   }

   Person(const Builder &builder)
      : id(builder.id), name(builder.name)
   {
   }
};

void Foo()
{
   Person p = Person::build().setId(2).setName("Derek Jeter");
}

これは、同等のコードとほぼ同じアセンブラーにコンパイルされます。

struct Person
{
   int id;
   std::string name;
};

Person p;
p.id = 2;
p.name = "Derek Jeter";
于 2010-02-26T16:46:23.580 に答える
1

「C++ と Java API は同じことをしているはずだ」というあなたの主張には根拠がありません。それらは同じことをするために文書化されていません。各出力言語は、.proto ファイルに記述された構造の異なる解釈を作成できます。その利点は、各言語で得られるものがその言語の慣用句であることです。たとえば、「C++ で Java を書いている」という感覚を最小限に抑えます。メッセージクラスごとに個別のビルダークラスがあれば、間違いなくそう感じるでしょう。

整数フィールドの場合、 protocfooからの C++ 出力には、指定されたメッセージのクラスにメソッドが含まれます。void set_foo(int32 value)

Java 出力では、代わりに2 つのクラスが生成されます。1 つはメッセージを直接表しますが、フィールドのゲッターしかありません。もう 1 つのクラスはビルダー クラスで、フィールドのセッターしかありません。

Python の出力はまだ異なります。生成されたクラスには、直接操作できるフィールドが含まれます。C、Haskell、Ruby のプラグインもかなり異なると思います。それらがすべて、ネットワーク上で同等のビットに変換できる構造を表すことができる限り、その仕事は完了です。これらは「プロトコル バッファ」であり、「API バッファ」ではないことに注意してください。

C++ プラグインのソースは、protocディストリビューションで提供されます。関数の戻り値の型を変更したい場合は、set_foo大歓迎です。私は通常、「オープン ソースなので、誰でも変更できます」というような回答は避けます。なぜなら、問題を解決するためだけに大きな変更を加えるために、まったく新しいプロジェクトを十分に習得するよう誰かに勧めることは、通常は役に立たないからです。ただし、この場合、それほど難しいとは思いません。最も難しい部分は、フィールドのセッターを生成するコードのセクションを見つけることです。それを見つけたら、必要な変更を行うのはおそらく簡単です。戻り値の型を変更しreturn *this、生成されたコードの末尾にステートメントを追加します。その後、 Hrnt'で指定されたスタイルでコードを記述できるはずです。.

于 2010-02-26T16:17:58.753 に答える
0

C ++では、メモリを明示的に管理する必要があります。これにより、イディオムを使用するのがさらに面倒になります。ビルダーのデストラクタを呼び出すか、オブジェクトbuild()の構築後に削除するためにメモリを保持する必要があります。Personどちらかが少し怖いです。

于 2010-02-19T07:47:22.873 に答える