使用默认参数的类构造函数是一种好习惯,还是应该使用单独的重载构造函数?例如:
// 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);
您更喜欢或推荐哪种风格?为什么?
答案 0 :(得分:81)
绝对是风格问题。我更喜欢带有默认参数的构造函数,只要参数有意义。标准中的类也使用它们,这对他们有利。
要注意的一件事是,如果除了一个参数之外的所有参数都有默认值,则可以从该参数类型隐式转换您的类。查看this thread了解详情。
答案 1 :(得分:33)
我会使用默认参数,特别是因为C ++不允许链式构造函数(所以你最终必须复制初始化列表,并且可能更多,为每次重载)。
也就是说,有一些带有默认参数的陷阱,包括可以内联常量的事实(从而成为类的二进制接口的一部分)。另一个值得注意的是,添加默认参数可以将显式多参数构造函数转换为隐式单参数构造函数:
class Vehicle {
public:
Vehicle(int wheels, std::string name = "Mini");
};
Vehicle x = 5; // this compiles just fine... did you really want it to?
答案 2 :(得分:13)
根据我的经验,默认参数在当时看起来很酷,让我的懒惰因素感到高兴,但接下来我正在使用课程,当默认开始时我感到很惊讶。所以我真的不认为这是一个好主意;最好有一个className :: className(),然后是className :: init( arglist )。只是为了可维护性。
答案 3 :(得分:13)
本讨论既适用于构造函数,也适用于方法和函数。
好处是你不需要为每种情况重载构造函数/方法/函数:
// Header
void doSomething(int i = 25) ;
// Source
void doSomething(int i)
{
// Do something with i
}
不好的是你必须在标题中声明你的默认值,所以你有一个隐藏的依赖:就像你更改内联函数的代码一样,如果你更改标题中的默认值,你需要使用此标头重新编译所有源,以确保它们将使用新的默认值。
如果不这样做,源仍然会使用旧的默认值。
好处是,如果您的函数没有内联,则可以通过选择一个函数的行为来控制源中的默认值。例如:
// Header
void doSomething() ;
void doSomething(int i) ;
// Source
void doSomething()
{
doSomething(25) ;
}
void doSomething(int i)
{
// Do something with i
}
问题是你必须维护多个构造函数/方法/函数及其转发。
答案 4 :(得分:6)
Sam's回答给出了默认参数更适合构造函数而不是重载的原因。我只想补充一点,C ++ - 0x将允许delegation从一个构造函数到另一个构造函数,从而消除了对默认值的需求。
答案 5 :(得分:4)
两种方法都有效。但是,如果您有一长串可选参数,请创建一个默认构造函数,然后让您的set函数返回对此的引用。然后链接设定者。
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);
}
现在,在构造对象时,您可以选择要覆盖哪些属性以及您已设置的属性是否已明确命名。更具可读性:))
此外,您不再需要记住构造函数的参数顺序。
答案 6 :(得分:3)
如果创建带参数的构造函数很糟糕(正如许多人所说的那样),那么使用默认参数创建它们会更糟。我最近开始认为ctor参数很糟糕,因为你的ctor逻辑应该尽可能小。如果有人传递一个没有任何意义的论据,你如何处理ctor中的错误处理?你可以抛出一个异常,这是个坏消息,除非你的所有调用者都准备在try块中包装任何“新”调用,或者设置一些“is-initialized”成员变量,这是一种肮脏的黑客攻击。 / p>
因此,确保传递到对象初始化阶段的参数的唯一方法是设置一个单独的initialize()方法,您可以在其中检查返回代码。
使用默认参数有两个原因:首先,如果你想在ctor中添加另一个参数,那么你就不会把它放在开头并改变整个API。此外,大多数程序员习惯于通过在实践中使用的方式来确定API - 对于在可能不存在正式文档的组织内部使用的非公共API,这是尤其 true。当其他程序员看到大多数调用都不包含任何参数时,他们也会这样做,仍然幸福地没有意识到默认参数对它们施加的默认行为。
另外,值得注意的是google C++ style guide避开两个ctor参数(除非绝对必要)和default arguments to functions or methods。
答案 7 :(得分:3)
还需要考虑的另一件事是该类是否可以在数组中使用:
foo bar[400];
在这种情况下,使用默认参数没有任何优势。
这肯定不起作用:
foo bar("david", 34)[400]; // NOPE
答案 8 :(得分:2)
我会使用默认参数,原因如下:您的示例假定ctor参数直接对应于成员变量。但是如果情况并非如此,那么在初始化对象之前必须处理参数。拥有一个共同的ctor是最好的方式。
答案 9 :(得分:2)
使用默认参数困扰我的一件事是你不能指定最后的参数,而是使用第一个参数的默认值。例如,在你的代码中,你不能创建一个没有名字但是给定年龄的Foo(但是,如果我没记错的话,这将在C ++ 0x中实现,具有统一的构造语法)。有时,这是有道理的,但它也可能非常尴尬。
在我看来,没有经验法则。 Personnaly,我倾向于使用多个重载的构造函数(或方法),除非只有最后一个参数需要一个默认值。
答案 10 :(得分:2)
主要是个人选择。但是,重载可以执行默认参数可以执行的任何操作,但不是反之亦然。
示例:
您可以使用重载来编写A(int x,foo& a)和A(int x),但是不能使用default参数来编写A(int x,foo& = null)。
一般规则是使用任何有意义的东西并使代码更具可读性。
答案 11 :(得分:0)
样式的问题,但正如Matt所说,绝对考虑使用默认参数标记构造函数,这将允许隐式转换为“显式”以避免意外的自动转换。这不是一个要求(如果你要创建一个你想隐式转换为的包装类,可能不是更好),但它可以防止错误。
我个人喜欢默认情况,因为我不喜欢重复的代码。 YMMV。