提问人: 提问时间:6/30/2009 最后编辑:Drew Noakes 更新时间:10/31/2023 访问量:167725
在 C++ 中按引用传递时参数的默认值
Default value to a parameter while passing by reference in C++
问:
是否可以在通过引用传递参数时为函数的参数提供默认值。在 C++ 中
例如,当我尝试声明一个函数时,如下所示:
virtual const ULONG Write(ULONG &State = 0, bool sequence = true);
当我这样做时,它给出了一个错误:
错误 C2440:“默认参数”:无法从“const int”转换为“unsigned long &” 不指向“const”的引用不能绑定到非左值
答:
您可以对常量引用执行此操作,但不能对非常量引用执行此操作。这是因为 C++ 不允许将临时值(在本例中为默认值)绑定到非常量引用。
一种方法是使用实际实例作为默认值:
static int AVAL = 1;
void f( int & x = AVAL ) {
// stuff
}
int main() {
f(); // equivalent to f(AVAL);
}
但这的实际用途非常有限。
评论
我认为不是,原因是默认值的计算结果为常量,并且通过引用传递的值必须能够更改,除非您也将其声明为常量引用。
评论
不,这是不可能的。
通过引用传递意味着函数可能会更改参数的值。如果参数不是由调用方提供的,而是来自默认常量,那么函数应该更改什么?
评论
在对你的回答的直接评论之一中已经说过了,但只是正式说明。要使用的是重载:
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'
不能将常量文本用于默认参数,原因与不能将常量文本用作函数调用的参数的原因相同。引用值必须有地址,常量引用值不需要(即它们可以是 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) 因为您需要能够更改函数内参数的值。
我非常怀疑在现代架构上传递一个无符号的长篇会大大减慢你的速度。因此,我假设您打算更改方法内部的值。编译器抱怨,因为常量无法更改,因为它是右值(错误消息中的“非左值”)和不可更改(错误消息中的“”)。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);
当然,我不太确定你在写什么或写在哪里。但这是另一个问题了。
仍然有旧的 C 提供可选参数的方式:一个指针,当不存在时可以为 NULL:
void write( int *optional = 0 ) {
if (optional) *optional = 5;
}
评论
void revealSelection(const ScrollAlignment& = ScrollAlignment::alignCenterIfNeeded, bool revealExtent = false);
这个小模板将帮助您:
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
另一种方式可能是:
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"
virtual const ULONG Write(ULONG &State = 0, bool sequence = true);
答案很简单,我不太擅长解释,但是如果您想将默认值传递给可能会在此函数中修改的非常量参数,请像这样使用它:
virtual const ULONG Write(ULONG &State = *(ULONG*)0, bool sequence =
> true);
评论
void f(const double& v = *(double*) NULL)
{
if (&v == NULL)
cout << "default" << endl;
else
cout << "other " << v << endl;
}
评论
如果出现 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 级”配置,例如:用户分页、导航首选项等。
可以使用 State 的 const 限定符:
virtual const ULONG Write(const ULONG &State = 0, bool sequence = true);
评论
还有一个相当肮脏的伎俩:
virtual const ULONG Write(ULONG &&State = 0, bool sequence = true);
在这种情况下,您必须使用以下命令来调用它:std::move
ULONG val = 0;
Write(std::move(val));
这只是一些有趣的解决方法,我完全不建议在实际代码中使用它!
我有一个解决方法,请参阅以下有关默认值的示例:int&
class Helper
{
public:
int x;
operator int&() { return x; }
};
// How to use it:
void foo(int &x = Helper())
{
}
您可以对所需的任何简单数据类型执行此操作,例如 , ...bool
double
定义 2 个重载函数。
virtual const ULONG Write(ULONG &State, bool sequence = true);
virtual const ULONG Write(bool sequence = true)
{
int State = 0;
return Write(State, sequence);
}
使用 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);
}
这有一个主要缺点,即收集错误消息的信息可能非常昂贵。 例如,询问数据库出了什么问题,可能需要另一次网络访问。 但请记住,原始函数也有同样的问题,因此这是一个很好的微优化机会。
对于绝大多数具有多个输出参数的新函数,我使用多个返回值。
评论
WxWidgets style guide says "don't use templates" and they have good reasons
<-螺丝 std::vector,我说