【问题标题】:Where to put default parameter value in C++? [duplicate]在 C++ 中将默认参数值放在哪里? [复制]
【发布时间】:2011-06-26 17:52:51
【问题描述】:

默认参数值在哪里?只是在函数定义,或声明,或两个地方?

【问题讨论】:

    标签: c++ default-arguments


    【解决方案1】:

    默认参数值必须出现在声明中,因为这是调用者看到的唯一内容。

    编辑:正如其他人指出的那样,您可以对定义有论据,但我建议编写所有代码,就好像那不是真的一样。 p>

    【讨论】:

    • +1:即使在技术上允许您选择,但在声明中这样做是唯一的自我记录方式。
    • 我想知道是否有任何情况下默认值是特定于实现的。这让我想知道其他事情:有可能把它混合起来吗?就像有一个参数在实现中设置了默认值,而在声明中设置了一个?不知道你为什么要这样做。但还是很好奇。
    • @AidanMueller 这不可能。默认值是由调用者根据它看到的声明传递的,而不是定义中指定的值。
    【解决方案2】:

    你可以做任何一个,但不能同时做。通常您在函数声明时执行此操作,然后所有调用者都可以使用该默认值。但是you can do that at function definition instead 然后只有看到定义的人才能使用默认值。

    【讨论】:

    • 这在技术上可能是正确的,但我不认为这是个好建议。
    • 如果你想变得非常可怕,实际上你可以两者都做,但参数不同。 :-)
    • @Bo Persson:好主意。除非我们已经有模板可以让你随心所欲。
    • @sharptooth: 和宏 :)
    • 为我做任何一个都会产生以下 g++ error: default argument given for parameter <x> of <fun(args)> [-fpermissive] ..... after previous specification in <fun(args)> [-fpermissive]
    【解决方案3】:

    C++将默认参数逻辑放在调用端,这意味着如果不能从调用位置计算出默认值表达式,则不能使用默认值。

    其他编译单元通常只包含声明,因此放置在定义中的默认值表达式只能用于定义编译单元本身(在定义之后,即编译器看到默认值表达式之后)。

    最有用的地方是在声明 (.h) 中,以便所有用户都能看到。

    有些人也喜欢在实现中添加默认值表达式(作为评论):

    void foo(int x = 42,
             int y = 21);
    
    void foo(int x /* = 42 */,
             int y /* = 21 */)
    {
       ...
    }
    

    但是,这意味着重复,并且会增加注释与代码不同步的可能性(比未注释的代码更糟糕的是什么?带有误导性 cmets 的代码!)。

    【讨论】:

    • 我对这些 cmets 没有问题,因为 99.99% 的时间参数只是简单的值初始化。
    【解决方案4】:

    虽然这是一个“旧”线程,但我仍然想在其中添加以下内容:

    我经历过下一个案例:

    • 在一个类的头文件中,我有
    int SetI2cSlaveAddress( UCHAR addr, bool force );
    
    • 在那个类的源文件中,我有
    int CI2cHal::SetI2cSlaveAddress( UCHAR addr, bool force = false )
    {
       ...
    }
    

    可以看出,我把参数“force”的默认值放在了类源文件中,而不是放在类头文件中。

    然后我在派生类中使用该函数如下(派生类以公共方式继承基类):

    SetI2cSlaveAddress( addr );

    假设它会将“force”参数设为“false”“理所当然”。

    但是,编译器(进入 c++11 模式)抱怨并给了我以下编译器错误:

    /home/.../mystuff/domoproject/lib/i2cdevs/max6956io.cpp: In member function 'void CMax6956Io::Init(unsigned char, unsigned char, unsigned int)':
    /home/.../mystuff/domoproject/lib/i2cdevs/max6956io.cpp:26:30: error: no matching function for call to 'CMax6956Io::SetI2cSlaveAddress(unsigned char&)'
    /home/.../mystuff/domoproject/lib/i2cdevs/max6956io.cpp:26:30: note: candidate is:
    In file included from /home/geertvc/mystuff/domoproject/lib/i2cdevs/../../include/i2cdevs/max6956io.h:35:0,
                     from /home/geertvc/mystuff/domoproject/lib/i2cdevs/max6956io.cpp:1:
    /home/.../mystuff/domoproject/lib/i2cdevs/../../include/i2chal/i2chal.h:65:9: note: int CI2cHal::SetI2cSlaveAddress(unsigned char, bool)
    /home/.../mystuff/domoproject/lib/i2cdevs/../../include/i2chal/i2chal.h:65:9: note:   candidate expects 2 arguments, 1 provided
    make[2]: *** [lib/i2cdevs/CMakeFiles/i2cdevs.dir/max6956io.cpp.o] Error 1
    make[1]: *** [lib/i2cdevs/CMakeFiles/i2cdevs.dir/all] Error 2
    make: *** [all] Error 2
    

    但是当我在基类的header文件中添加默认参数时:

    int SetI2cSlaveAddress( UCHAR addr, bool force = false );

    并将其从基类的源文件中删除:

    int CI2cHal::SetI2cSlaveAddress( UCHAR addr, bool force )

    然后编译器很高兴,所有代码都按预期工作(我可以给函数SetI2cSlaveAddress() 提供一两个参数)!

    因此,不仅对于类的用户而言,将参数的默认值放在头文件中很重要,而且在编译和功能方面,这显然是必须的!

    【讨论】:

    • 这是因为调用者需要知道函数接受哪些参数,所以调用者看到的声明(通常在头文件中)需要有这些信息(包括默认值之类的东西)。
    【解决方案5】:

    如果函数是公开的——非成员、公共或受保护的——那么调用者应该知道它们,并且默认值必须在标头中。

    如果函数是私有的并且是外联的,那么将默认值放在实现文件中是有意义的,因为这允许不触发客户端重新编译的更改(对于共享的低级库有时是一个严重的问题在企业规模发展中)。也就是说,这肯定会造成混淆,并且在标题中以更直观的方式呈现 API 具有文档价值,因此请选择您的折衷方案 - 尽管无论哪种方式都没有令人信服的理由,一致性是主要的事情。

    【讨论】:

      【解决方案6】:

      还有一点我没有找到任何人提到:

      如果你有虚方法,每个声明都可以有自己的默认值!

      这取决于您调用的接口将使用哪个值。

      ideone 上的示例

      struct iface
      {
          virtual void test(int a = 0) { std::cout << a; }
      };
      
      struct impl : public iface
      {
          virtual void test(int a = 5) override { std::cout << a; }
      };
      
      int main()
      {
          impl d;
          d.test();
          iface* a = &d;
          a->test();
      }
      

      它打印50

      我强烈反对你这样使用它

      【讨论】:

      • 我们不能默认将参数分配给对象吗?喜欢void test(obj, a, b = a);
      • 这是因为,即使在虚函数中,参数的默认值也是静态绑定的:这意味着选择参数的默认值来评估变量的静态类型。在这种情况下,即使 a 指向 impl 对象,a 也是 iface 的 ptr,并且当您调用 a->test() 时,编译器会使用 iface 的默认参数。这就是为什么在覆盖中更改默认参数值是一个可怕的想法。 Scott Meyers 在他的一本书中谈到了这一点。
      【解决方案7】:

      声明通常是最“有用的”,但这取决于您希望如何使用该类。

      两者都无效。

      【讨论】:

        【解决方案8】:

        好问题... 我发现编码人员通常使用声明来声明默认值。根据编译器,我一直坚持一种方式(或警告)或另一种方式

        void testFunct(int nVal1, int nVal2=500);
        void testFunct(int nVal1, int nVal2)
        {
            using namespace std;
            cout << nVal1 << << nVal2 << endl;
        }
        

        【讨论】:

          【解决方案9】:

          您可以在任何一个中执行(根据标准),但请记住,如果您的代码在包含默认参数的定义之前看到没有默认参数的声明,则可能会出现编译错误。

          例如,如果您包含包含函数声明但没有默认参数列表的标头,则编译器将查找该原型,因为它不知道您的默认参数值,因此原型将不匹配。

          如果您要在定义中添加带有默认参数的函数,请包含该文件,但我不建议这样做。

          【讨论】:

            【解决方案10】:

            再加一点。带有默认参数的函数声明应该从右到左排序和从上到下。

            例如在下面的函数声明中,如果您更改声明顺序,那么编译器会给您一个缺少默认参数的错误。原因编译器允许您在同一范围内将函数声明与默认参数分开,但它应该按照从 RIGHT 到 LEFT(默认参数)和从 TOP 到 BOTTOM(函数声明默认参数的顺序)的顺序。

            //declaration
            void function(char const *msg, bool three, bool two, bool one = false);
            void function(char const *msg, bool three = true, bool two, bool one); // Error 
            void function(char const *msg, bool three, bool two = true, bool one); // OK
            //void function(char const *msg, bool three = true, bool two, bool one); // OK
            
            int main() {
                function("Using only one Default Argument", false, true);
                function("Using Two Default Arguments", false);
                function("Using Three Default Arguments");
                return 0;
            }
            
            //definition
            void function(char const *msg, bool three, bool two, bool one ) {
                std::cout<<msg<<" "<<three<<" "<<two<<" "<<one<<std::endl;
            }
            

            【讨论】:

            • 相关语言:[C++11: 8.3.6/4]: [..] 在给定的函数声明中,带有默认参数的参数后面的每个参数都应在此或先前的声明 [..]
            猜你喜欢
            • 2018-09-07
            • 1970-01-01
            • 1970-01-01
            • 2012-04-24
            • 2019-09-20
            • 2011-06-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多