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&&
。
唯一的方法是
- 正在 return 调用函数
const T
。 (无论如何,拥有这样一个 return 类型是个坏主意。)
- 正在 return 调用函数
const T&&
。 (同上。)
- 故意转换为
const&&
。 (无论如何你都不会这样做。)
- 从
&&
隐式转换。 (这不会发生,因为您有一个通过 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
时请三思。
我有一个 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&&
。
唯一的方法是
- 正在 return 调用函数
const T
。 (无论如何,拥有这样一个 return 类型是个坏主意。) - 正在 return 调用函数
const T&&
。 (同上。) - 故意转换为
const&&
。 (无论如何你都不会这样做。) - 从
&&
隐式转换。 (这不会发生,因为您有一个通过 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
时请三思。