C ++构造函数的默认参数
有一个使用默认参数的类构造函数是好的做法,还是应该使用单独的重载构造函数? 例如:
// Use this... class foo { private: std::string name_; unsigned int age_; public: foo(const std::string& name = "", const unsigned int age = 0) : name_(name), age_(age) { ... } }; // Or this? class foo { private: std::string name_; unsigned int age_; public: foo() : name_(""), age_(0) { } foo(const std::string& name, const unsigned int age) : name_(name), age_(age) { ... } };
这两个版本似乎工作,例如:
foo f1; foo f2("Name", 30);
你喜欢或推荐哪种风格,为什么?
绝对是一个风格问题。 我更喜欢默认参数的构造函数,只要参数有意义。 标准中的课程也使用它们,这对他们有利。
有一点需要注意的是,如果除了一个参数之外,所有参数都有默认值,那么您的类可以隐式地从该参数types转换。 看看这个线程的更多信息。
我会去默认的参数,尤其是因为C + +不让你链构造函数(所以你最终不得不复制初始化列表,可能更多,每个重载)。
也就是说,有一些缺省参数的问题,包括常量可能被内联(从而成为类的二进制接口的一部分)的事实。 另一个要注意的是,添加默认参数可以将一个显式的多参数构造函数转换为一个隐式的单参数构造函数:
class Vehicle { public: Vehicle(int wheels, std::string name = "Mini"); }; Vehicle x = 5; // this compiles just fine... did you really want it to?
根据我的经验,当时的默认参数看起来很酷,并且让我的懒惰因素很开心,但是随后我使用这个类,而且默认情况下,我感到惊讶。所以我不认为这是一个好主意; 最好有一个className :: className(),然后是一个className :: init( arglist )。 只是为了维护性的缘故。
这个讨论既适用于构造函数,也适用于方法和函数。
使用默认参数?
好处是你不需要为每个情况重载构造函数/方法/函数:
// Header void doSomething(int i = 25) ; // Source void doSomething(int i) { // Do something with i }
坏处是你必须在头文件中声明你的默认值,所以你有一个隐藏的依赖:就像当你改变一个内联函数的代码一样,如果你改变头的默认值,你将需要重新编译所有的源代码使用这个头部来确保他们将使用新的默认值。
如果你不这样做,源代码仍将使用旧的默认值。
使用重载的构造函数/方法/函数?
好处是,如果你的函数没有内联,你可以通过select一个函数的行为来控制源代码中的默认值。 例如:
// Header void doSomething() ; void doSomething(int i) ; // Source void doSomething() { doSomething(25) ; } void doSomething(int i) { // Do something with i }
问题是你必须维护多个构造函数/方法/函数和它们的转发。
山姆的回答给出了默认参数比构造函数更适合而不是重载的原因。 我只想补充说,C ++ – 0x将允许从一个构造函数委托给另一个,从而消除了默认值的需要。
如果用参数创build构造函数是不好的(很多人会争论),那么使用默认参数来创build构造函数就更糟了。 我最近开始意识到ctor的论点是不好的,因为你的ctor逻辑应该尽可能的小 。 如何处理error handling?如果有人通过一个没有任何意义的论点? 你可以抛出一个exception,这是个坏消息,除非你所有的调用者准备在try块中包含任何“新”调用,或者设置一些“is-initialized”成员variables,这是一种肮脏的黑客攻击。
因此,确保传入对象初始化阶段的参数的唯一方法是设置一个单独的initialize()方法,您可以在其中检查返回代码。
默认参数的使用是不好的,原因有两个: 首先,如果你想向ctor添加另一个参数,那么你就不得不把它放在一开始,并改变整个API。 此外,大多数程序员习惯于通过实践中使用的方式来计算API, 尤其是在组织内部使用非公开API的情况下,正式文档可能不存在。 当其他程序员看到大多数的调用不包含任何参数时,他们会做同样的事情,保持幸福的意识,默认的行为默认的行为强加给他们。
另外,值得注意的是, 谷歌C ++风格指南避开ctor参数(除非绝对必要),以及函数或方法的默认参数 。
两种方法都有效。 但是,如果你有一个可选参数的长列表使一个默认的构造函数,然后让你的设置函数返回一个引用。 然后locking设备。
class Thingy2 { public: enum Color{red,gree,blue}; Thingy2(); Thingy2 & color(Color); Color color()const; Thingy2 & length(double); double length()const; Thingy2 & width(double); double width()const; Thingy2 & height(double); double height()const; Thingy2 & rotationX(double); double rotationX()const; Thingy2 & rotatationY(double); double rotatationY()const; Thingy2 & rotationZ(double); double rotationZ()const; } main() { // gets default rotations Thingy2 * foo=new Thingy2().color(ret) .length(1).width(4).height(9) // gets default color and sizes Thingy2 * bar=new Thingy2() .rotationX(0.0).rotationY(PI),rotationZ(0.5*PI); // everything specified. Thingy2 * thing=new Thingy2().color(ret) .length(1).width(4).height(9) .rotationX(0.0).rotationY(PI),rotationZ(0.5*PI); }
现在,在构build对象时,您可以select要覆盖哪些属性以及设置了哪些属性。 更可读:)
而且,您不再需要记住构造函数的参数顺序。
我会去默认参数,因为这个原因:你的例子假定ctor参数直接对应于成员variables。 但是如果情况并非如此,那么在对象初始化之前必须处理这些参数。 有一个共同的ctor将是最好的方式去。
主要是个人select。 然而,重载可以做任何默认参数可以做的,但反之亦然。
例:
你可以使用重载来写A(int x,foo&a)和A(int x),但是你不能使用默认参数来写A(int x,foo&= null)。
一般的规则是使用任何有意义的东西,并使代码更具可读性。
使用默认参数困扰我的一件事是,你不能指定最后的参数,但使用第一个参数的默认值。 例如,在你的代码中,你不能创build一个没有名字,但给定年龄的Foo(但是,如果我没有记错的话,这将是可能的C ++ 0x,统一的构造语法)。 有时候,这是有道理的,但也可能非常尴尬。
在我看来,没有经验法则。 Personnaly,我倾向于使用多个重载构造函数(或方法),除非只有最后一个参数需要一个默认值。
还有一件事要考虑的是这个类是否可以在一个数组中使用:
foo bar[400];
在这种情况下,使用默认参数没有优势。
这肯定不会工作:
foo bar("david", 34)[400]; // NOPE
风格的问题,但正如马特说,绝对考虑标记构造函数的默认参数,这将允许隐式转换为“显式”,以避免意外的自动转换。 这不是一个需求(如果你想创build一个你想隐式转换的包装类,这可能不是最好的),但它可以防止错误。
我个人喜欢默认情况下,因为我不喜欢重复的代码。 因人而异。