在C ++中通过引用传递时参数的默认值


116

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

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

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

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

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


96
幸好,我们不受Google样式指南的约束。

23
“不要这样做。Google风格指南(及其他)禁止通过引用进行非const传递”,我认为风格指南包含许多主观部分。这看起来像其中之一。
Johannes Schaub-litb

13
WxWidgets style guide says "don't use templates" and they have good reasons<-螺丝std :: vector,我说
Johannes Schaub-litb

7
@jeffamaphone:Google风格指南还说不要使用流。您是否也建议避免使用它们?
rlbond

10
锤子是用来放置螺丝起子的可怕工具,但是用钉子还是相当有用的。您可能会滥用功能这一事实仅应警告您可能存在的陷阱,而不能禁止其使用。
DavidRodríguez-dribeas 2010年

Answers:


101

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

一种解决方法是将实际实例用作默认实例:

static int AVAL = 1;

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

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

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


如果我将其设为const,是否可以将其他地址传递给函数?还是国家的地址始终为0,如此毫无意义?

如果使用引用,则不会传递地址。

3
boost :: array到救援空虚f(int&x = boost :: array <int,1>()[0]){..} :)
Johannes Schaub-litb

1
如果不能解决实际通过的问题?

2
@Sony参考。认为它是地址是不对的。如果需要地址,请使用指针。

32

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

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
}

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


19
有些人认为默认参数比过载的巨量乘法更可怕。
男孩先生2010年

2
也许到最后,您会说: d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
Pietro

4
关于最后一条语句:从C ++ 11开始,可以使用委派构造函数从另一个构造函数中调用一个构造函数,以避免代码重复。
Fred Schoen 2015年

25

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

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

我非常喜欢这种方法,非常简短。如果有时您想返回一些通常不需要的其他信息,统计信息等,则非常实用。
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);

ByRef实时的实例化在哪里存储?它不是一个临时对象,在离开某个范围(例如构造函数)时会被破坏吗?
安德鲁畅

1
@AndrewCheong它的全部意图是在现场构造,并在生产线完成后销毁。这是在通话过程中公开引用的一种方法,这样即使期望引用,也可以提供默认参数。此代码用于活动项目中,并按预期运行。
Mike Weir 2014年

7

不,不可能。

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


3
传统的FORTRAN方法本来是要更改0的值,但这在C ++中不会发生。
David Thornley,2009年

7

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

我高度怀疑,在现代建筑上花了无数的时间会大大降低您的速度。因此,我假设您打算更改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&。但是您不会从中获得很大的性能优势,因此我建议将其更改为非引用ULONG。我注意到您已经返回ULONG,并且暗中怀疑它的值就是State经过任何必要的修改后的值。在这种情况下,我只需要这样声明方法:

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

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


6

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

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指针。我喜欢空对象方法。它使调用者的生活变得更加轻松,而又不会使被调用者的代码复杂化。


恕我直言,这种风格很“臭”。只有在构造函数中使用默认参数时,它才是真正合理的。在其他所有情况下,函数重载都提供了完全相同的语义,而没有与默认值相关的任何其他问题。
理查德·科登

1

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


2
默认值不是“评估为常数”。

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

有用。基本上,它是访问引用值以检查NULL指向引用(逻辑上没有NULL引用,只有指向的内容为NULL)。最重要的是,如果您使用的是对“引用”进行操作的库,则通常会有一些API(例如“ isNull()”)用于对库特定的引用变量进行相同的操作。并建议在这种情况下使用这些API。
parasrish

1

如果是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() ) {

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


1

可以使用const限定符表示State:

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

传递很长时间的const ref是没有意义的,甚至是荒谬的,并且没有实现OP想要的功能。
Jim Balter


0

还有一个很肮脏的把戏:

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

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

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

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


0

我对此有一种解决方法,请参见以下有关默认值的示例int&

class Helper
{
public:
    int x;
    operator int&() { return x; }
};

// How to use it:
void foo(int &x = Helper())
{

}

您可以针对所需的任何琐碎数据类型执行此操作,例如booldouble...


0

定义2个过载功能。

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

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

-3

虚拟常量ULONG Write(ULONG&State = 0,bool sequence = true);

答案很简单,我的解释也不是很好,但是如果您要将默认值传递给非const参数,可能会在此函数中对其进行修改,则可以像这样使用它:

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

3
取消引用NULL指针是非法的。在某些情况下这可能会起作用,但这是非法的。在此处了解更多信息parashift.com/c++-faq-lite/references.html#faq-8.7
Spo1ler 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.