【问题标题】:Default parameters with C++ constructors [closed]C++ 构造函数的默认参数[关闭]
【发布时间】:2010-09-16 07:16:12
【问题描述】:

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

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

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

【问题讨论】:

  • 我个人在很多项目中都大量使用了它。至于是不是“最好的”,我说不上来,但过去肯定对我非常有帮助。
  • 无论哪种方式都没什么大不了的。然而,一个警告是,任何只接受一个参数的构造函数都应该是显式的,以避免讨厌的隐式转换。
  • 这两种方法都可以,只要你不混合使用它们 - 否则你可能会得到一些令人讨厌的惊喜。
  • 您的第二个选项有效吗?在我看来这是错误的。您正在重载构造函数,因此您应该声明两个函数 foo() 和 foo(string, unsigned)... 然后您将拥有第一个选项。
  • C++ 指南在这个主题上很明确:这两个选项都没有,但第二个更好。制作几个重载的构造函数和prefer in-class initializers

标签: c++ constructor coding-style overloading


【解决方案1】:

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

需要注意的一点是,如果您对除一个参数以外的所有参数都有默认值,那么您的类可以从该参数类型隐式转换。查看this thread 了解更多信息。

【讨论】:

    【解决方案2】:

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

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

    class Vehicle {
    public:
      Vehicle(int wheels, std::string name = "Mini");
    };
    
    Vehicle x = 5;  // this compiles just fine... did you really want it to?
    

    【讨论】:

    • 在这种情况下,将“explicit”关键字添加到构造函数中。它应该阻止 Vehicle x = 5; 编译。
    • 而现在,5 年后,C++11 在类定义中添加了委托构造函数和默认成员初始化,使得默认参数变通方法变得不必要:en.wikipedia.org/wiki/C%2B%2B11#Object_construction_improvement
    【解决方案3】:

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

    使用默认参数?

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

    // 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】:

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

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

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

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

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

          【讨论】:

          • 我喜欢这种链接设置者的能力;无论是在实例化时还是用于以后的更新。你有没有尝试过接受一个结构体并从中挑选出属性到对象中 - 或者只是将结构体用作对象内的数据保留器?
          【解决方案7】:

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

          例子:

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

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

          【讨论】:

            【解决方案8】:

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

            foo bar[400];
            

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

            这肯定行不通:

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

            【讨论】:

              【解决方案9】:

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

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

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

              另外,值得注意的是,google C++ style guide 避开了两个 ctor 参数(除非绝对必要)和 default arguments to functions or methods

              【讨论】:

                【解决方案10】:

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

                【讨论】:

                  【解决方案11】:

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

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

                  【讨论】:

                    【解决方案12】:

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

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

                    【讨论】:

                      猜你喜欢
                      • 2016-07-06
                      • 1970-01-01
                      • 2014-07-09
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2014-05-08
                      • 2012-02-26
                      • 2012-06-30
                      相关资源
                      最近更新 更多