134

拥有一个使用默认参数的类构造函数是一种好习惯,还是应该使用单独的重载构造函数?例如:

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

你喜欢或推荐哪种风格,为什么?

4

12 回答 12

96

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

需要注意的一件事是,如果您对除一个参数之外的所有参数都有默认值,那么您的类可以从该参数类型隐式转换。查看此线程以获取更多信息。

于 2008-10-09T14:59:28.587 回答
43

我会使用默认参数,特别是因为 C++ 不允许您链接构造函数(因此您最终不得不为每个重载复制初始化列表,甚至可能更多)。

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

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

Vehicle x = 5;  // this compiles just fine... did you really want it to?
于 2008-10-09T15:07:28.150 回答
19

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

使用默认参数?

好消息是您不需要为每种情况重载构造函数/方法/函数:

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

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

于 2008-10-09T16:14:37.843 回答
13

以我的经验,默认参数当时看起来很酷,让我的懒惰因素很开心,但后来我正在使用这个类,当默认参数启动时我很惊讶。所以我真的不认为这是一个好主意; 最好有一个 className::className() 和一个 className::init( arglist )。只是为了可维护性优势。

于 2008-10-09T15:04:50.650 回答
7

Sam 的回答给出了默认参数更适合构造函数而不是重载的原因。我只想补充一点,C++-0x 将允许从一个构造函数委托给另一个构造函数,从而消除对默认值的需要。

于 2008-10-09T17:01:27.467 回答
5

任何一种方法都有效。但是,如果您有一长串可选参数,请创建一个默认构造函数,然后让您的 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);
}

现在,在构造对象时,您可以选择要覆盖的属性以及明确命名的属性。更具可读性:)

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

于 2008-10-09T16:18:50.110 回答
3

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

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

使用默认参数不好有两个原因;首先,如果您想向 ctor 添加另一个参数,那么您将无法将其放在开头并更改整个 API。此外,大多数程序员习惯于通过实际使用的方式来计算 API——对于在可能不存在正式文档的组织内部使用的非公共 API尤其如此。当其他程序员看到大多数调用不包含任何参数时,他们也会这样做,并且很高兴地不知道您的默认参数强加给他们的默认行为。

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

于 2008-10-09T15:57:55.397 回答
3

主要是个人选择。但是,重载可以做任何默认参数可以做的事情,但反之则不行。

例子:

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

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

于 2008-10-10T00:55:09.367 回答
3

要考虑的另一件事是该类是否可以在数组中使用:

foo bar[400];

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

这肯定行不通:

foo bar("david", 34)[400]; // NOPE
于 2010-02-08T05:16:33.143 回答
2

出于这个原因,我会使用默认参数:您的示例假定 ctor 参数直接对应于成员变量。但是如果不是这样的话,你必须在初始化对象之前处理参数。拥有一个共同的 ctor 将是最好的方法。

于 2008-10-09T15:09:06.493 回答
2

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

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

于 2008-10-09T15:10:07.927 回答
0

风格问题,但正如马特所说,绝对考虑使用默认参数标记构造函数,这将允许隐式转换为“显式”以避免意外的自动转换。这不是必需的(如果您要创建要隐式转换为的包装类,则可能不是优选的),但它可以防止错误。

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

于 2008-10-09T16:18:33.793 回答