使用 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);

你喜欢或推荐哪种款式? 为什么?

289958 次浏览

绝对是风格问题。我更喜欢带有默认参数的构造函数,只要参数有意义。标准类也使用它们,这对它们有利。

需要注意的一点是,如果除了一个参数之外,所有类都有默认值,则可以从该参数类型隐式转换类。查看 这根线了解更多信息。

根据我的经验,默认参数在当时看起来很酷,并且让我的惰性因素感到满意,但是接下来我会使用这个类,当默认参数生效时,我会感到惊讶。所以我不认为这是一个好主意; 最好使用 className: : className ()和 className: : init (Arglist)。只是为了保证可维护性。

我将使用缺省参数,特别是因为 C + + 不允许您链式构造函数(所以您最终必须为每个重载复制初始化器列表,可能还要复制更多)。

也就是说,存在一些具有默认参数的陷阱,包括常量可能是内联的(从而成为类的二进制接口的一部分)。另一个需要注意的是,添加默认参数可以将显式的多参数构造函数转换为隐式的单参数构造函数:

class Vehicle {
public:
Vehicle(int wheels, std::string name = "Mini");
};


Vehicle x = 5;  // this compiles just fine... did you really want it to?

出于这个原因,我将使用缺省参数: 您的示例假设 ctor 参数直接对应于成员变量。但是,如果情况并非如此,并且您必须在初始化对象之前处理参数,那么该怎么办呢。有一个共同的反应堆将是最好的方式去。

使用缺省参数困扰我的一个问题是,您不能指定最后一个参数,而是使用第一个参数的缺省值。例如,在您的代码中,您不能创建一个没有名称但有给定年龄的 Foo (但是,如果我没记错的话,这在 C + + 0x 中使用统一的构造语法是可能的)。有时候,这是有道理的,但也可能真的很尴尬。

在我看来,没有经验法则。就个人而言,我倾向于使用多个重载构造函数(或方法) ,除非只有最后一个参数需要默认值。

如果使用参数创建构造函数是不好的(正如许多人认为的那样) ,那么使用默认参数创建构造函数就更糟糕了。我最近开始认为 ctor 参数是不好的,因为你的 ctor 逻辑应该是 尽可能少。如何处理 ctor 中的错误处理,是否应该传入一个没有任何意义的参数?你可以抛出一个异常,这是一个坏消息,除非你的所有调用者都准备好在 try 块中包装任何“新”调用,或者设置一些“ is-initialization”成员变量,这是一种肮脏的黑客行为。

因此,确保传递到对象初始化阶段的参数的唯一方法是设置一个单独的 initialize ()方法,在该方法中可以检查返回代码。

默认参数的使用有两个原因: 首先,如果您想向 ctor 中添加另一个参数,那么您必须将它放在开头并更改整个 API。此外,大多数程序员习惯于通过在实践中使用的方式来计算一个 API ——对于组织内部使用的非公共 API 来说,这是 尤其是的真实情况,因为组织内部可能不存在正式的文档。当其他程序员看到大多数调用不包含任何参数时,他们也会做同样的事情,完全不知道默认参数强加给他们的默认行为。

另外,值得注意的是,谷歌 C + + 风格指南避免使用 ctor 参数(除非绝对必要)和 函数或方法的默认参数

此讨论既适用于构造函数,也适用于方法和函数。

使用默认参数?

好的一面是你不需要为每种情况重载构造函数/方法/函数:

// 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
}

问题是您必须维护多个构造函数/方法/函数及其转发。

风格问题,但正如 Matt 所说,一定要考虑使用默认参数标记构造函数,这将允许隐式转换为“显式”,以避免意外的自动转换。这不是一个必要条件(如果您想要隐式地转换为包装类,这可能不是最好的选择) ,但是它可以防止错误。

我个人喜欢在适当的时候使用默认值,因为我不喜欢重复的代码。

两种方法都行。但是如果你有一个长长的可选参数列表,那么你可以创建一个缺省构造函数,然后让你的 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);
}

现在,在构造对象时,您可以选择要重写的属性以及已设置的属性都是显式命名的。可读性更强:)

而且,您不再需要记住构造函数的参数顺序。

Sam 的 回答给出了这样一个理由: 对于构造函数来说,默认参数比重载更可取。我只想补充一点,C + +-0x 将允许 代表团从一个构造函数转换到另一个构造函数,从而消除了对默认值的需求。

主要是个人选择。然而,超载可以做任何默认参数可以做的事情,而 没有反之亦然。

例如:

可以使用重载来编写 A (int x,foo & a)和 A (int x) ,但不能使用默认参数来编写 A (int x,foo & = null)。

一般的规则是使用任何有意义的东西,并使代码更具可读性。

另一个需要考虑的问题是,该类是否可以在数组中使用:

foo bar[400];

在这种情况下,使用默认参数没有任何好处。

这肯定行不通:

foo bar("david", 34)[400]; // NOPE