Java与C ++的构建者?

在Google的Protocol Buffer API for Java中,他们使用这些创建对象的很好的构建器(参见此处 ):

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 ++和Java API应该做同样的事情,所以我想知道他们为什么不在C ++中使用构建器。 是否有语言原因,即它不是惯用的,或者在C ++中不受欢迎? 或者可能只是编写C ++版协议缓冲区的人的个人偏好?

在C ++中实现类似的东西的正确方法将使用返回* this的引用的setter。

 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")); 

如果需要单独的构建器类,那么也可以这样做。 当然,这些构建器应该以堆栈forms分配。

虽然我已经在C ++代码中看到了这种流畅的界面风格的例子,但我会选择“非惯用语”。

这可能是因为有很多方法可以解决同样的潜在问题。 通常,这里要解决的问题是命名参​​数(或者更确切地说是缺少参数)。 可以说更像C ++的解决方案可能是Boost的参数库 。

您声称“C ++和Java API应该做同样的事情”是没有根据的。 他们没有记录做同样的事情。 每种输出语言都可以创建.proto文件中描述的结构的不同解释。 这样做的好处是,您在每种语言中获得的内容都是该语言的惯用 。 它最大限度地减少了你用“用C ++编写Java”的感觉。 如果每个消息类都有一个单独的构建器类,那肯定是我的感觉。

对于整数字段foovoid set_foo(int32 value)的C ++输出将在给定消息的类中包含方法void set_foo(int32 value)

Java输出将生成两个类。 一个直接表示消息,但只有该字段的getter。 另一个类是构建器类,只有该字段的setter。

Python输出仍然不同。 生成的类将包含可以直接操作的字段。 我希望C,Haskell和Ruby的插件也大不相同。 只要它们都可以代表一个可以转换为线上等效位的结构,它们就可以完成它们的工作。 请记住,这些是“协议缓冲区”,而不是“API缓冲区”。

C ++插件的源代码随protoc发行版一起提供。 如果要更改set_foo函数的返回类型,欢迎您这样做。 我通常会避免相应的回答,“它是开源的,所以任何人都可以对其进行修改”,因为建议有人学习一个全新的项目,以便为解决问题做出重大改变通常不会有帮助。 但是,我不认为在这种情况下会非常困难。 最难的部分是找到为字段生成setter的代码部分。 一旦找到它,进行所需的更改可能会很简单。 更改返回类型,并将return *this语句添加到生成的代码的末尾。 然后,您应该能够以Hrnt的答案中给出的样式编写代码。

跟进我的评论……

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

差异部分是惯用的,但也是C ++库更加优化的结果。

您在问题中未注意到的一件事是protoc发出的Java类是不可变的,因此必须具有(可能)非常长的参数列表和没有setter方法的构造函数。 不可变模式通常在Java中使用,以避免与multithreading相关的复杂性(以性能为代价),并且构建器模式用于避免在大型构造函数调用时眯眼并且需要在同一时间提供所有值的痛苦在代码中指出。

protoc发出的C ++类不是不可变的,其设计使得对象可以在多个消息接收上重用(请参阅C ++基础知识页面上的“优化提示”部分); 因此它们使用起来更难,更危险,但效率更高。

当然,两个实现可以用相同的样式编写,但是开发人员似乎觉得易用性对于Java来说更重要,性能对于C ++来说更重要,可能反映了这些语言的使用模式。谷歌。

在C ++中,你必须明确地管理内存,这可能会使得习惯用法更加痛苦 – build()必须为构建器调用析构函数,否则你必须保留它以在构造Person对象后删除它。 要么对我有点害怕。