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

Default value to a parameter while passing by reference in C++

提问人: 提问时间:6/30/2009 最后编辑:Drew Noakes 更新时间:10/31/2023 访问量:167725

问:

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

例如,当我尝试声明一个函数时,如下所示:

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

当我这样做时,它给出了一个错误:

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

C++ 按引用传递 默认值

评论

111赞 6/30/2009
值得庆幸的是,我们不受 Google 风格指南的约束。
28赞 Johannes Schaub - litb 6/30/2009
“不要这样做。谷歌风格指南(和其他人)禁止非常量通过引用“我认为众所周知的风格指南包含许多主观部分。这看起来像其中之一。
16赞 Johannes Schaub - litb 6/30/2009
WxWidgets style guide says "don't use templates" and they have good reasons<-螺丝 std::vector,我说
7赞 rlbond 6/30/2009
@jeffamaphone:谷歌风格指南也说不要使用流媒体。你也会建议避免它们吗?
12赞 David Rodríguez - dribeas 1/19/2010
锤子是放置螺丝刀的可怕工具,但它非常适合钉子。您可以滥用某个功能这一事实应该只会警告您可能存在的陷阱,但不会禁止使用它。

答:

126赞 anon 6/30/2009 #1

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

一种方法是使用实际实例作为默认值:

static int AVAL = 1;

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

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

但这的实际用途非常有限。

评论

0赞 6/30/2009
如果我让它成为常量,它会允许我向函数传递不同的地址吗?还是国家地址总是 0 而毫无意义?
0赞 6/30/2009
如果使用引用,则不会传递地址。
3赞 Johannes Schaub - litb 6/30/2009
boost::array 来救援 void f(int &x = boost::array<int,1>()[0]) { .. } :)
1赞 6/30/2009
如果没有解决实际通过的问题?
2赞 6/30/2009
@Sony 参考。把它想象成一个地址是很痛苦的。如果需要地址,请使用指针。
1赞 ilya n. 6/30/2009 #2

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

评论

2赞 6/30/2009
默认值不是“作为常量计算”的。
7赞 NascarEd 6/30/2009 #3

不,这是不可能的。

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

评论

3赞 David Thornley 6/30/2009
传统的 FORTRAN 方法是更改 0 的值,但这在 C++ 中不会发生。
44赞 4 revs, 2 users 98%Richard Corden #4

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

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

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

评论

24赞 Mr. Boy 1/19/2010
有些人认为默认参数不如大量重载那么可怕。
2赞 Pietro 6/20/2011
也许在最后,你会想到:d.f2(); // f2(int) called with '0' b.f2(); // f2(int) called with '0'
4赞 Fred Schoen 11/17/2015
最后一条语句:从 C++ 11 开始,您可以使用委托构造函数从另一个构造函数调用另一个构造函数,以避免代码重复。
6赞 deft_code 6/30/2009 #5

不能将常量文本用于默认参数,原因与不能将常量文本用作函数调用的参数的原因相同。引用值必须有地址,常量引用值不需要(即它们可以是 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 指针。我喜欢空对象方法。它使呼叫者的生活更轻松,而不会使被叫者代码变得非常复杂。

评论

0赞 Richard Corden 6/30/2009
恕我直言,这种风格很“臭”。默认参数唯一真正合理的时间是在构造函数中使用时。在所有其他情况下,函数重载都提供完全相同的语义,而不会出现与默认值相关的任何其他问题。
8赞 Max Lybbert 6/30/2009 #6

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

我非常怀疑在现代架构上传递一个无符号的长篇会大大减慢你的速度。因此,我假设您打算更改方法内部的值。编译器抱怨,因为常量无法更改,因为它是右值(错误消息中的“非左值”)和不可更改(错误消息中的“”)。State0const

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

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

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

如果您实际上不打算在方法内部进行更改,则可以简单地将其更改为 .但是你不会从中获得很大的性能优势,所以我建议将其更改为非参考。我注意到您已经返回了一个 ,并且我偷偷地怀疑它的值是任何需要修改后的值。在这种情况下,我将简单地声明该方法:Stateconst ULONG&ULONGULONGState

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

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

32赞 David Rodríguez - dribeas 1/19/2010 #7

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

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

评论

1赞 uLoop 2/12/2017
我真的很喜欢这种方法,非常简短和简单。如果有时您想返回一些您通常不需要的额外信息、统计数据等,那就超级实用了。
0赞 Abhijeet Kandalkar 4/27/2011 #8
void revealSelection(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, bool revealExtent = false);
11赞 Mike Weir 10/29/2011 #9

这个小模板将帮助您:

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赞 Andrew Cheong 8/19/2014
在内存方面,实时实例化在哪里?它不是一个临时对象,在离开某个范围(如构造函数)时会被破坏吗?ByRef
2赞 Mike Weir 8/19/2014
@AndrewCheong 它的全部目的是在现场建造,并在线路完工后销毁。这是一种在调用持续时间内公开引用的方法,以便即使需要引用也可以提供默认参数。此代码用于活动项目,并按预期运行。
1赞 JJTh 1/31/2012 #10

另一种方式可能是:

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"
-4赞 bogdan 6/14/2012 #11

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

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

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

评论

3赞 Spo1ler 6/16/2012
取消引用 NULL 指针是非法的。这在某些情况下可能有效,但这是非法的。在此处阅读更多内容 parashift.com/c++-faq-lite/references.html#faq-8.7
1赞 Waxrat 10/19/2013 #12
void f(const double& v = *(double*) NULL)
{
  if (&v == NULL)
    cout << "default" << endl;
  else
    cout << "other " << v << endl;
}

评论

0赞 parasrish 3/20/2016
它有效。基本上,它是访问用于检查 NULL 指向引用的引用值(逻辑上没有 NULL 引用,只是您指向的是 NULL)。最重要的是,如果您使用一些对“引用”进行操作的库,那么通常会有一些 API,例如“isNull()”,用于对特定于库的引用变量执行相同的操作。并建议在这种情况下使用这些 API。
1赞 wdavilaneto 5/14/2014 #13

如果出现 OO...说给定类具有 和 “Default” 意味着这个 Default(值)必须声明,然后可以作为默认参数 usd ex:

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 级”配置,例如:用户分页、导航首选项等。

1赞 vic 12/13/2014 #14

可以使用 State 的 const 限定符:

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

评论

0赞 Jim Balter 10/9/2017
将长作为 const ref 传递是毫无意义甚至荒谬的,并且没有达到 OP 想要的。
0赞 avtomaton 10/13/2016 #15

还有一个相当肮脏的伎俩:

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

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

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

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

0赞 Omar Natour 12/2/2018 #16

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

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

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

}

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

0赞 Zhang 8/3/2020 #17

定义 2 个重载函数。

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

virtual const ULONG Write(bool sequence = true)
{
    int State = 0;
    return Write(State, sequence);
}
0赞 tsukehiki 10/31/2023 #18

使用 MSVC 将我们的代码库从 C++17 升级到 C++20,Microsoft 现在终于强制执行此规则。

的语义是可选的输出参数。 我经常看到这个具体的例子,其中需要多个输出参数:void foo(int& optOutParam = 0)optOutParam

bool Initialise(Window& wnd, Context& ctxt, std::string& error = "") {
// ...
}

返回值指示函数是否成功,而 error 包含函数可能失败的原因。 不幸的是,明确禁止引用,否则这也是一个很好的解决方案。std::optional

std::optional<int&> opt; // compiler error

重构的一个好方法是让函数返回所有值,并避免使用引用参数作为输出。

std::tuple<Window, Context, std::string> Initialise();

void modernCpp() {
  auto[wnd, ctxt, errorMsg] = Initialise();
  if(!errorMsg.empty()) {
    exit(1);
  }
}

void olderCpp() {
  Window wnd;
  Context ctxt;
  std::string errorMsg;
  std::tie(wnd, ctxt, errorMsg) = Initialise();

  // or
  tuple<Window, Context, std::string> result = Initialise();
}

不幸的是,这意味着还必须重构每个调用站点,这在大型代码库中可能是一项艰巨的工作。 为了处理这种情况,我使用了一个 r 值重载,它丢弃了错误消息。 届时无需更改呼叫站点。

bool Initialise(Window& wnd, Context& ctxt, std::string& error) {
  // default value removed
}
bool Initialise(Window& wnd, Context& ctxt, std::string&& discardedErrorMsg = "") {
  // call overload above
  return Initialise(wnd, ctxt, discardedErrorMsg);
}

这有一个主要缺点,即收集错误消息的信息可能非常昂贵。 例如,询问数据库出了什么问题,可能需要另一次网络访问。 但请记住,原始函数也有同样的问题,因此这是一个很好的微优化机会。

对于绝大多数具有多个输出参数的新函数,我使用多个返回值。