使用右值引用作为默认参数

11

我想创建一个函数,它接受一个可选的对象引用,并在函数执行期间为其创建一个对象(如果未提供该对象)。

void Foo(Bar& b = Bar()) { /* stuff */ }

当然,这是无效的代码,因为在此上下文中,Bar 不能隐式转换为 Bar 引用。引用不能是 const,因为函数内部对 b 进行了更改。

您可以通过使用右值引用来解决这个问题,即:

void Foo(Bar&& b = Bar()) { /* stuff */ }

这是否是正确使用右值引用的方式?现在调用者必须在它们的 Bar 参数上调用 std::move,即使我没有清除传递的 Bar 的意图,而通常当你传递右值时会这样做。


为什么不使用静态默认对象呢? - Deduplicator
4
这句话的意思是:如何理解 void Foo(Bar & b = stay(Bar()))? 其中使用了 template <typename T> T & stay(T && t) { return t; } - Kerrek SB
1
@MattMcNabb:抱歉,但我不太明白ctor(也许还有dtor)非平凡对使用静态默认对象有什么影响,除了在人为制造的边缘情况下(静态初始化顺序混乱)之外。你能详细说明一下吗? - Deduplicator
为什么在标题中提到通用引用,当与它们没有任何关系?此外,“浅拷贝”对于移动语义来说是一个非常具有误导性的术语。 - Jonathan Wakely
2
@MattKline:通用引用是与rvalue、lvalue、const rvalue或const lvalue匹配的引用参数。它们是接收模板类型为&&的模板函数。这很反直觉,因为它们与在其签名中也使用&&的rvalue引用参数没有任何关系。 - Mooing Duck
显示剩余6条评论
2个回答

13
void Foo(Bar&& b = Bar()) { /* stuff */ }

那确实是r-value引用的有效用法,但它并没有反映出实际语义,因此是“设计有缺陷”的。
你想做的是使用一个提供默认参数的转发函数,像这样:
void Foo(Bar& b) { /* stuff */ }
void Foo() { Bar b{}; Foo(b); }

或者使用静态默认参数(请注意,这将始终重用相同的对象):
template<class T> decltype(T{})& default_object() {static T x{}; return x;}
void Foo(Bar& b = default_object<Bar>()) { /* stuff */ }

或者像KerrekSB在评论中建议的那样(我添加了constexpr),使用这个危险的模板函数:
template<class T> constexpr T& no_move(T&& t) { return t; }
void Foo(Bar& b = no_move(Bar{})) { /* stuff */ }

9

所以你有一个函数,它需要使用一个in-out参数来传递信息给调用者。

但你希望这个参数是可选的。

那么你的解决方案就是让参数看起来像是一个in参数,要求调用者移动参数(这通常意味着他们会失去任何状态,或者可能处于未指定的状态)。这是一种糟糕的设计,你会因为函数内部实现的方便而混淆调用者的视听。你应该为用户而不是实现者设计API。

你可以采取Deduplicator建议的方法,将其拆分为两个函数,一个提供虚拟对象作为in-out参数并在使用后丢弃:

void Foo(Bar& b) { /* stuff */ }
void Foo() { Bar dummy{}; Foo(dummy); }

如果您需要一个可以为空的引用,那么请不要使用引用,而是使用正确的语言特性来传递可以为空的参数:

void Foo(Bar* b) { /* stuff, updating b if not null */ }

当然,这也是一个有效的选项。可能更清洁,也可能不是。另外:我列出了两个你没有提到的选项。 - Deduplicator
@Deduplicator,我不喜欢另外两个选项,所以没有参考它们 :) 你的重载函数建议得到了我的赞同。 - Jonathan Wakely

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