【问题标题】:Default value to a parameter while passing by reference in C++在 C++ 中通过引用传递时参数的默认值
【发布时间】:2010-11-06 18:35:21
【问题描述】:

当我们通过引用传递参数时,是否可以给函数的参数一个默认值。在 C++ 中

例如,当我尝试声明如下函数时:

virtual const ULONG Write(ULONG &State = 0, bool sequence = true);

当我这样做时会报错:

错误 C2440:“默认参数”:无法从“const int”转换为“unsigned long &” 不是 'const' 的引用不能绑定到非左值

【问题讨论】:

  • 幸运的是,我们不受 Google 风格指南的约束。
  • “不要这样做。Google 样式指南(和其他)禁止非常量通过引用传递”我认为样式指南包含许多主观部分。这看起来像其中之一。
  • WxWidgets style guide says "don't use templates" and they have good reasons
  • @jeffamaphone:谷歌风格指南也说不要使用流。你会建议也避开它们吗?
  • 锤子是一种用来放置螺丝刀的可怕工具,但它非常适用于钉子。您可以滥用某项功能这一事实应该只警告您可能存在的陷阱,而不是禁止其使用。

标签: c++ pass-by-reference default-value


【解决方案1】:

您可以为 const 引用执行此操作,但不能为非 const 引用。这是因为 C++ 不允许将临时值(在这种情况下为默认值)绑定到非常量引用。

解决此问题的一种方法是使用实​​际实例作为默认值:

static int AVAL = 1;

void f( int & x = AVAL ) {
   // stuff
} 

int main() {
     f();       // equivalent to f(AVAL);
}

但这在实际用途中非常有限。

【讨论】:

  • 如果我将其设为 const,是否允许我将不同的地址传递给函数?还是 State 的地址会一直为 0 就没有意义了?
  • 如果您使用引用,则不会传递地址。
  • boost::array 到救援 void f(int &x = boost::array()[0]) { .. } :)
  • 如果没有解决实际通过的问题?
  • @Sony 参考。将其视为地址是错误的。如果您想要地址,请使用指针。
【解决方案2】:

已经在您的答案的直接 cmet 之一中说过,但只是为了正式声明。您要使用的是重载:

virtual const ULONG Write(ULONG &State, bool sequence);
inline const ULONG Write()
{
  ULONG state;
  bool sequence = true;
  Write (state, sequence);
}

使用函数重载还有其他好处。首先,您可以默认任何您希望的参数:

class A {}; 
class B {}; 
class C {};

void foo (A const &, B const &, C const &);
void foo (B const &, C const &); // A defaulted
void foo (A const &, C const &); // B defaulted
void foo (C const &); // A & B defaulted etc...

还可以在派生类中重新定义虚函数的默认参数,从而避免重载:

class Base {
public:
  virtual void f1 (int i = 0);  // default '0'

  virtual void f2 (int);
  inline void f2 () {
    f2(0);                      // equivalent to default of '0'
  }
};

class Derived : public Base{
public:
  virtual void f1 (int i = 10);  // default '10'

  using Base::f2;
  virtual void f2 (int);
};

void bar ()
{
  Derived d;
  Base & b (d);
  d.f1 ();   // '10' used
  b.f1 ();   // '0' used

  d.f2 ();   // f1(int) called with '0' 
  b.f2 ();   // f1(int) called with '0
}

只有一种情况需要使用默认值,那就是构造函数。不能从另一个构造函数调用一个构造函数,因此这种技术在这种情况下不起作用。

【讨论】:

  • 有些人认为默认参数没有重载的巨大倍增那么可怕。
  • 也许最后,你说:d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
  • 关于最后一条语句:从 C++ 11 开始,您可以使用委托构造函数从另一个构造函数调用一个构造函数,以避免代码重复。
【解决方案3】:

仍然有提供可选参数的旧 C 方法:一个指针,当不存在时可以为 NULL:

void write( int *optional = 0 ) {
    if (optional) *optional = 5;
}

【讨论】:

  • 我非常喜欢这种方法,非常简短。如果有时您想返回一些您通常不需要的额外信息、统计数据等,则非常实用。
【解决方案4】:

这个小模板会帮助你:

template<typename T> class ByRef {
public:
    ByRef() {
    }

    ByRef(const T value) : mValue(value) {
    }

    operator T&() const {
        return((T&)mValue);
    }

private:
    T mValue;
};

然后你就可以:

virtual const ULONG Write(ULONG &State = ByRef<ULONG>(0), bool sequence = true);

【讨论】:

  • ByRef 的实例化在哪里存活,内存方面?它不是一个临时对象,在离开某个范围(如构造函数)时会被销毁吗?
  • @AndrewCheong 它的全部意图是在现场构建并在线路完成时销毁。这是一种在调用期间公开引用的方法,以便即使在需要引用时也可以提供默认参数。此代码在活动项目中使用并按预期运行。
【解决方案5】:

通过引用传递参数有两个原因:(1) 出于性能考虑(在这种情况下,您希望通过 const 引用传递)和 (2) 因为您需要能够更改函数内部参数的值.

我非常怀疑在现代架构上传递一个 unsigned long 是否会让你的速度太慢。所以我假设您打算在方法内更改State 的值。编译器抱怨是因为常量 0 无法更改,因为它是右值(错误消息中的“非左值”)且不可更改(错误消息中的 const)。

简单地说,你想要一个可以改变传递的参数的方法,但默认情况下你想要传递一个不能改变的参数。

换句话说,非const 引用必须引用实际变量。函数签名 (0) 中的默认值不是实变量。您遇到了与以下相同的问题:

struct Foo {
    virtual ULONG Write(ULONG& State, bool sequence = true);
};

Foo f;
ULONG s = 5;
f.Write(s); // perfectly OK, because s is a real variable
f.Write(0); // compiler error, 0 is not a real variable
            // if the value of 0 were changed in the function,
            // I would have no way to refer to the new value

如果您实际上不打算在方法内更改State,您可以简单地将其更改为const ULONG&amp;。但是您不会从中获得很大的性能优势,因此我建议将其更改为非参考 ULONG。我注意到您已经返回了ULONG,并且我偷偷怀疑它的值是经过任何必要修改后的State 的值。在这种情况下,我会简单地声明该方法:

// returns value of State
virtual ULONG Write(ULONG State = 0, bool sequence = true);

当然,我不太确定你在写什么或写到哪里。但这是另一个问题。

【讨论】:

    【解决方案6】:

    不,这不可能。

    通过引用传递意味着函数可能会更改参数的值。如果参数不是调用者提供的,而是来自默认常量,那么函数应该改变什么?

    【讨论】:

    • 传统的 FORTRAN 方法是更改​​ 0 的值,但这在 C++ 中不会发生。
    【解决方案7】:

    您不能将常量文字用作默认参数,原因与您不能将其用作函数调用的参数相同。引用值必须有地址,常量引用值不需要(即它们可以是右值或常量字面量)。

    int* foo (int& i )
    {
       return &i;
    }
    
    foo(0); // compiler error.
    
    const int* bar ( const int& i )
    {
       return &i;
    }
    
    bar(0); // ok.
    

    确保你的默认值有一个地址,你很好。

    int null_object = 0;
    
    int Write(int &state = null_object, bool sequence = true)
    {
       if( &state == &null_object )
       {
          // called with default paramter
          return sequence? 1: rand();
       }
       else
       {
          // called with user parameter
          state += sequence? 1: rand();
          return state;
       }
    }
    

    我已经使用过这种模式几次,我的参数可以是变量或空值。常规方法是让用户在这种情况下传递一个指针。如果他们不想让你填写值,他们会传入一个 NULL 指针。我喜欢空对象方法。它使调用者的生活更轻松,而不会使被调用者代码非常复杂。

    【讨论】:

    • 恕我直言,这种风格相当“臭”。唯一一次真正合理的默认参数是在构造函数中使用它时。在所有其他情况下,函数重载都提供完全相同的语义,而没有任何与默认值相关的其他问题。
    【解决方案8】:

    我认为不是,原因是默认值被评估为常量,并且通过引用传递的值必须能够更改,除非您也将其声明为常量引用。

    【讨论】:

    • 默认值不是“被评估为常量”。
    【解决方案9】:

    另一种方式可能如下:

    virtual const ULONG Write(ULONG &State, bool sequence = true);
    
    // wrapper
    const ULONG Write(bool sequence = true)
    {
       ULONG dummy;
       return Write(dummy, sequence);
    }
    

    那么以下调用是可能的:

    ULONG State;
    object->Write(State, false); // sequence is false, "returns" State
    object->Write(State); // assumes sequence = true, "returns" State
    object->Write(false); // sequence is false, no "return"
    object->Write(); // assumes sequence = true, no "return"
    

    【讨论】:

      【解决方案10】:
      void f(const double& v = *(double*) NULL)
      {
        if (&v == NULL)
          cout << "default" << endl;
        else
          cout << "other " << v << endl;
      }
      

      【讨论】:

      • 它有效。基本上,它是访问引用值以检查 NULL 指向引用(逻辑上没有 NULL 引用,只有你指向的是 NULL)。最重要的是,如果您正在使用一些对“引用”进行操作的库,那么通常会有一些 API,例如“isNull()”,用于对库特定的引用变量执行相同的操作。并且建议在这种情况下使用这些 API。
      【解决方案11】:

      在 OO 的情况下......要说给定类具有并且“默认”意味着该默认(值)必须相应地声明,然后可以用作默认参数例如:

      class Pagination {
      public:
          int currentPage;
          //...
          Pagination() {
              currentPage = 1;
              //...
          }
          // your Default Pagination
          static Pagination& Default() {
              static Pagination pag;
              return pag;
          }
      };
      

      关于你的方法...

       shared_ptr<vector<Auditoria> > 
       findByFilter(Auditoria& audit, Pagination& pagination = Pagination::Default() ) {
      

      这个解决方案非常合适,因为在这种情况下,“全局默认分页”是一个单一的“参考”值。您还可以在运行时更改默认值,例如“全局级别”配置,例如:用户分页导航首选项等。

      【讨论】:

        【解决方案12】:

        状态的 const 限定符是可能的:

        virtual const ULONG Write(const ULONG &State = 0, bool sequence = true);
        

        【讨论】:

        • 将 long 作为 const ref 传递是没有意义的,甚至是荒谬的,并且没有达到 OP 想要的效果。
        【解决方案13】:
        void revealSelection(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, bool revealExtent = false);
        

        【讨论】:

          【解决方案14】:

          还有一个相当肮脏的技巧:

          virtual const ULONG Write(ULONG &&State = 0, bool sequence = true);
          

          在这种情况下,您必须使用std::move 调用它:

          ULONG val = 0;
          Write(std::move(val));
          

          这只是一些有趣的解决方法,我完全不建议在实际代码中使用它!

          【讨论】:

            【解决方案15】:

            我有一个解决方法,请参阅以下关于int&amp; 默认值的示例:

            class Helper
            {
            public:
                int x;
                operator int&() { return x; }
            };
            
            // How to use it:
            void foo(int &x = Helper())
            {
            
            }
            

            您可以为任何您想要的琐碎数据类型执行此操作,例如booldouble ...

            【讨论】:

              【解决方案16】:

              定义 2 个重载函数。

              virtual const ULONG Write(ULONG &State, bool sequence = true);
              
              virtual const ULONG Write(bool sequence = true)
              {
                  int State = 0;
                  return Write(State, sequence);
              }
              

              【讨论】:

                【解决方案17】:

                virtual const ULONG Write(ULONG &State = 0, bool sequence = true);

                答案很简单,我不太擅长解释,但是如果您想将默认值传递给可能会在此函数中修改的非常量参数,请像这样使用它:

                virtual const ULONG Write(ULONG &State = *(ULONG*)0, bool sequence =
                > true);
                

                【讨论】:

                猜你喜欢
                • 2010-12-22
                • 1970-01-01
                • 1970-01-01
                • 2011-12-24
                • 2016-01-02
                • 2010-11-01
                • 2014-04-09
                • 2016-05-13
                相关资源
                最近更新 更多