在Google的Protocol Buffer API for Java中,他们使用这些构建对象的漂亮构建器(请参阅here):
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不使用此类构建器(请参阅here)
C ++和Java API应该做同样的事情,所以我想知道他们为什么不在C ++中使用构建器。是否有语言原因,即它不是惯用的,或者在C ++中不受欢迎?或者可能只是编写C ++版协议缓冲区的人的个人偏好?
答案 0 :(得分:6)
在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"));
如果需要单独的构建器类,那么也可以这样做。应该分配这些建造者 当然是在堆栈中。
答案 1 :(得分:4)
我会选择“非惯用语”,虽然我已经在C ++代码中看到了这种流畅的界面风格的例子。
这可能是因为有很多方法可以解决同样的潜在问题。通常,这里要解决的问题是命名参数(或者更确切地说是缺少参数)。可以说这个问题的更多 C ++ - 解决方案可能是Boost's Parameter library。
答案 2 :(得分:1)
你声称“C ++和Java API应该做同样的事情”是没有根据的。他们没有记录做同样的事情。每种输出语言都可以创建.proto文件中描述的结构的不同解释。这样做的好处是,您在每种语言中获得的内容都是该语言的 。它最大限度地减少了你用“用C ++编写Java”的感觉。如果每个消息类都有一个单独的构建器类,那肯定是我的感觉。
对于整数字段foo
, protoc 的C ++输出将在给定消息的类中包含方法void set_foo(int32 value)
。
Java输出将生成两个类。一个直接表示消息,但只有该字段的getter。另一个类是构建器类,只有该字段的setter。
Python输出仍然不同。生成的类将包含可以直接操作的字段。我希望C,Haskell和Ruby的插件也大不相同。只要它们都可以代表一个可以转换为线上等效位的结构,它们就可以完成它们的工作。请记住,这些是“协议缓冲区”,而不是“API缓冲区”。
C ++插件的源代码随 protoc 发行版一起提供。如果要更改set_foo
函数的返回类型,欢迎您这样做。我通常会避免相应的回答,“它是开源的,所以任何人都可以修改它”,因为建议有人学习一个全新的项目,以便做出重大改变只是为了解决问题通常没有用。但是,我不认为在这种情况下会非常困难。最难的部分是找到为字段生成setter的代码部分。一旦你找到了,你所需要的改变可能会很简单。更改返回类型,并在生成的代码末尾添加return *this
语句。然后,您应该能够以Hrnt's answer中给出的样式编写代码。
答案 3 :(得分: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";
答案 4 :(得分:1)
差异部分是惯用的,但也是C ++库更加优化的结果。
您在问题中未注意到的一件事是,protoc发出的Java类是不可变的,因此必须具有(可能)非常长的参数列表且没有setter方法的构造函数。不可变模式通常在Java中使用,以避免与多线程相关的复杂性(以性能为代价),并且构建器模式用于避免在大型构造函数调用时眯眼并且需要在同一时间提供所有值的痛苦在代码中指出。
protoc发出的C ++类不是不可变的,其设计使得对象可以在多个消息接收中重用(请参阅C++ Basics Page上的“优化提示”部分);因此它们使用起来更难,更危险,但效率更高。
当然,两个实现可以用相同的样式编写,但是开发人员似乎觉得易用性对于Java来说更重要,性能对于C ++来说更重要,也许反映了使用模式谷歌的这些语言。
答案 5 :(得分:0)
在C ++中你必须明确地管理内存,这可能会使得习惯用法变得更加痛苦 - build()
必须为构建器调用析构函数,否则你必须保留它以便在删除后删除它构造Person
对象。
要么对我有点害怕。