引用限定的成员函数设计中断常量右值

Ref-qualified member functions design breaks on const rvalues

本文关键字:中断 常量 函数 成员 引用      更新时间:2023-10-16

我有一个Maybe类,它是一个基于堆栈的类,可能包含给定的类型。 我们有一些函数返回一个包含可变或常量引用的Maybe。 这主要是为了减少样板、查找和不需要的副本。

Map<String, Foo> map;
// Normal C++
auto it = map.find("foo");
if (it != map.end())
  doStuff(*it);
// Has an extra lookup, bad
if (map.contains("foo"))
  doStuff(map.get("foo"));
// Uses Maybe
if (auto val = map.maybe("foo"))
  doStuff(*val);
// Also possible:
// apply calls the function with *this as argument if this is valid
map.maybe("foo").apply(&doStuff);

但是,当map是临时的时,这是有问题的:

Map<String, Foo> map;
Map<String, Foo> getMap() { return map; } // Returns a copy of map
if (auto val = getMap().maybe("foo")) // Returns Maybe<Foo&> to temporary
  doStuff(*val); // Very bad, *val has already been deleted

另一方面,因为Maybe<Foo>可以从Maybe<Foo&>构造(一般来说,Maybe<T2>可以从Maybe<T>构造,如果T2可以从T构造),那么如果我写这个,这不是问题。

if (Maybe<Foo> val = getMap().maybe("foo"))
  doStuff(*val); // OK, val contains a copy

一位同事偶然遇到这个问题之后,我有一个好主意,即在可能返回 Maybe<T&> 的地方使用限定的成员函数来返回Maybe<T>,如果它是一个右值。

Maybe<Val> Map<Key, Val>::maybe(Key const& key) &&;
Maybe<Val const&> Map<Key, Val>::maybe(Key const& key) const&;
Maybe<Val &> Map<Key, Val>::maybe(Key const& key) &;

但是我很难弄清楚在const&&;的情况下该怎么做

如果没有,它将调用const&版本,这很糟糕,因为这会返回引用。

我考虑过将&&版本设为const&&版本以避免重复;但是,我需要复制,而不是移动内部值。 而且我对const&&的语义了解不够,不知道内部const_cast是否可以接受,或者这是否会导致疯狂和歇斯底里,而我突变了const&&。 如果没有必要,我宁愿不必编写此函数的两个副本。

在这种情况下,什么是正常的最佳实践? 我需要编写所有 4 个函数吗? 或者我可以理智地摆脱 3 吗?

有没有更好的方法来避免这种悬而未决的参考问题? 这只是一个问题,因为auto通常会剥离引用,因此您不会意外地将引用带到临时值,但是因为Maybe的类型已经是一个普通值,它只是包装引用类型,有可能搬起石头砸自己的脚。 只是说"那么在这种情况下不要使用auto"是非常诱人的,但仍然很容易不小心搞砸,我宁愿做错事很难。

无论如何,

你都不可能得到一个好的代码const&&

唯一的方法是

  1. 调用返回 const T 的函数。(无论如何,拥有这样的返回类型是一个坏主意。
  2. 调用返回 const T&& 的函数。(迪托。
  3. 故意投射到const&&.(反正你不会这样做。
  4. &&隐式转换。(这不会发生,因为您有一个通过 r 值引用接受的重载。

因此,如果你想让你的API防弹,正确的方法就是delete重载:

Maybe<Val &> maybe(Key const& key) const&& = delete;
我认为

这不是一个真正的问题。考虑该计划

#include <map>
#include <cstdio>
int main() {
  int& x = std::map<int, int>{{3, 4}}[3];
  printf("%dn", x);
}

地图销毁后,参考x将悬而未决(使最后一行的行为未定义)。标准库不采取任何措施来防止这种情况。

我也从未听说过有人不小心犯了这种错误。

对于您的地图,情况相同。

IMO,根据地图的值类别返回Maybe<Val>Maybe<Val&>太混乱了。每次在临时对象上调用.maybe时,请三思而后行。