C++中按引用传递时为参数设置默认值

143

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

例如,当我尝试声明一个函数:

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

当我这样做时,它会出现错误:

错误 C2440:'default argument':无法将 'const int' 转换为 'unsigned long &' 不能将非lvalue的引用绑定到非const


111
感谢上天,我们不必遵守 Google 的样式指南。 - anon
28
不要这样做。Google的风格指南(以及其他指南)禁止非const引用传递。我认为风格指南被认为包含许多主观部分,这似乎就是其中之一。 - Johannes Schaub - litb
16
WxWidgets的样式指南称“不要使用模板”,并且他们有很好的理由。 <- 离谱的std::vector,我说 - Johannes Schaub - litb
7
谷歌风格指南也建议不要使用流(streams),您是否建议避免使用它们? - rlbond
12
锤子是一个可怕的工具来放置螺丝刀,但是它在钉子方面非常有用。您可以误用某个功能这一事实只应该警告您可能会遇到的问题,而不是禁止其使用。 - David Rodríguez - dribeas
显示剩余15条评论
18个回答

126

你可以使用const引用来实现,但不能使用非const引用。这是因为C++不允许将临时对象(在这种情况下是默认值)绑定到非const引用。

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

static int AVAL = 1;

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

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

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


3
boost::array来拯救 void f(int &x = boost::array<int,1>()[0]) { .. } :)(翻译说明:这句话是一段代码,意思是将boost::array用于函数参数默认值,让变量x的默认值为boost::array<int,1>数组的第一个元素。最后一句表情符号表示开发者的喜悦心情) - Johannes Schaub - litb
1
如果不解决实际传递的内容,那么有什么意义呢? - Sony
2
@Sony是一个引用。把它看作地址是错误的。如果你需要一个地址,请使用指针。 - anon
我发现重载一个可能返回多个值的函数很有用,但是调用者可能不关心其中一些值。调用者可以简单地不指定它们,而不必创建一个本地变量。 - Claudiu
@JohannesSchaub-litb 这个问题有 C++17 的解决方案吗? - Janus Troelsen
显示剩余6条评论

44

已经在对你的回答的一个直接评论中提到过了,但是为了正式说明。你想要使用的是重载:

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

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


Using function overloads also have additional benefits. Firstly you can default any argument you wish:
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'
}
  

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


24
有些人认为默认参数比过多地重载函数更容易理解。 - Mr. Boy
2
也许最后你的意思是:d.f2(); // 调用带有参数'0'的f2(int)函数 b.f2(); // 调用带有参数'0'的f2(int)函数 - Pietro
4
从C++11开始,你可以使用委派构造函数来调用一个构造函数以避免代码重复。 - Fred Schoen

32

还有一种旧的C方法提供可选参数:使用指针,当参数不存在时可以为NULL:

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

1
我非常喜欢这种方法,非常简短和简单。如果有时您想返回一些额外的信息、统计数据等通常不需要的内容,那么这种方法非常实用。 - uLoop

11
这个小模板将会帮助你:

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

1
ByRef 的实例在内存中存在于哪里?它不是一个临时对象,在离开某个范围(比如构造函数)时会被销毁吗? - Andrew Cheong
2
它的完整意图是在现场构建并在行完成时被销毁。它是一种暴露引用的方式,以便在期望引用时可以提供默认参数,并在调用期间为其提供持续时间。此代码用于一个活动项目中,并按预期工作。 - Mike Weir

8
有两个原因需要通过引用传递参数:(1)出于性能考虑(此时您想要通过const引用传递),以及(2)因为您需要在函数内部更改参数的值。
我非常怀疑在现代架构上传递无符号长整型会使您的速度变慢。因此,我假设您打算在方法内部更改“State”的值。编译器发出投诉,因为常量“0”不能被更改,因为它是一个rvalue(在错误消息中为“non-lvalue”)并且不可更改(在错误消息中为“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&。但这样做对性能提升帮助不大,所以我建议把它改成非引用ULONG。我注意到你已经在返回ULONG,我有一个狡猾的想法,即其值是任何必要修改后的State的值。如果是这种情况,我会将方法声明如下:
// returns value of State
virtual ULONG Write(ULONG State = 0, bool sequence = true);

当然,我不太确定你在写什么或者写给谁。但这是另一个时间的另一个问题。

7

不,这是不可能的。

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


3
传统的FORTRAN方法是改变0的值,但在C++中这种情况不会发生。 - David Thornley

6

你不能使用常量字面值作为默认参数,原因与将其用作函数调用的参数相同。引用值必须具有地址,而常量引用值不需要(即它们可以是r-values或常量字面值)。

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。传统的方法是让用户传递一个指针来处理这种情况。如果他们不想填充值,则传递一个NULL指针。我喜欢使用null对象方法。它使调用者的生活更加轻松,而不会过于复杂化被调用者的代码。


在我看来,这种风格相当“臭”。唯一合理使用默认参数的情况是在构造函数中。在其他任何情况下,函数重载都提供完全相同的语义,而没有与默认参数相关的任何其他问题。 - Richard Corden

1

另一种方法可能是以下方式:

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"

1
void f(const double& v = *(double*) NULL)
{
  if (&v == NULL)
    cout << "default" << endl;
  else
    cout << "other " << v << endl;
}

它有效。基本上,它是访问引用值以检查空指针引用(逻辑上不存在空引用,只是你所指向的是空)。此外,如果您正在使用一些操作“引用”的库,则通常会有一些API,如“isNull()”,用于执行相同的操作以检查库特定的引用变量。建议在这种情况下使用这些API。 - parasrish

1

在面向对象编程中,如果说一个类有一个“默认值”,那么这个默认值必须被相应地声明,然后可以作为默认参数使用,例如:

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() ) {

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接