Ref 限定的成员函数设计打破了 const rvalues

Ref-qualified member functions design breaks on const rvalues

我有一个 Maybe class,它是一个基于堆栈的 class,可能包含给定的类型。我们有某些函数 return a 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&> 构造(一般来说,如果 T2 是可构造的, Maybe<T2> 可以从 Maybe<T> 构造from T) 然后如果我写这个,这不是问题。

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

在一位同事偶然发现这个问题后,我灵机一动,在可能 return a Maybe<T&> 到 return a [=20 的地方使用 ref-qualified 成员函数=] 相反,如果它是一个右值。

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& 版本,这很糟糕,因为那会 return 一个参考。

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

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

有没有更好的方法来避免这个悬空引用问题?这只是一个问题,因为 auto 通常会去除引用,所以你不会不小心引用一个临时值,但是因为 Maybe 的类型已经是一个普通值,它只是包装了一个参考类型,就有可能搬起石头砸自己的脚。就说"well don't use auto in that case then,"很诱人,但一不小心还是很容易搞砸,宁愿做错事也难

无论如何你都不可能在好的代码中得到 const&&

唯一的方法是

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

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

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

我认为这不是真正的问题。考虑程序

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

引用 x 将在地图被销毁后悬空(使最后一行成为未定义的行为)。标准库没有采取任何措施来防止这种情况。

我也没听说过有人不小心犯这种错误的。

用你的地图,也是一样的情况。

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