代理对象和移动语义有问题。它们并不总是结合得很好。在我的容器类中,我有一个重载的 operator[]
,它在容器内使用惰性元素创建。因此,operator[]
返回一个代理对象,该对象保留对容器的现有元素的引用或潜在新元素的位置。这个想法是,无论何时以特定方式访问代理对象或将某些东西分配给它时,都会在该地方的容器中自动创建一个新元素。
问题出在返回代理对象的方式上:
Proxy operator [](std::string name);
我被迫按值返回,因为我希望调用者管理代理的生命周期。按值返回的问题在于 operator []
调用的结果是一个 prvalue,这意味着无论何时调用函数来初始化或分配一个新值,它总是会被移动对象:
/// Element of container identified by name "foo" is moved from.
Element foo(container["foo"]);
/// Same with bar...
foo = container["bar"];
我希望我的代理可以移动分配给一个元素,以获得流畅的开发体验。但我希望它以一种通常的、预期的方式出现,就像这样:
/// Element is copied
Element foo1 = container["foo"];
/// Element is moved
Element foo2 = std::move(container["foo"]);
就我所知,它可以通过第二个代理对象 MovableProxy
来实现。根据这个策略,我想将 Proxy
配置为只能复制。然后我想将 std::move
专门化为 Proxy
类型,以便在提供 rvalue 时返回 MovableProxy
或 Proxy
的 lvalue:
namespace std
{
MovableProxy move(Proxy & foo)
{
...
}
MovableProxy move(Proxy && foo)
{
...
}
} // namespace std
那么,为我自己的类专门化模板函数 std::move
的想法有多糟糕?拐角处有哪些可怕的陷阱?
最佳答案
您的代码是重载 move
的示例, 没有专门化它,那是永远不合法的。
只要您的特化遵循 std 对相关模板的任何规范,对 std 中的大多数模板进行特化是合法的。然而,重载不与专门化相同。
重载违反了将新符号插入 std
的规则.它使您的程序格式错误,无需诊断。
我可能会考虑写一个 notstd::move
转发到std::move
并具有额外类型的重载,或启用移动的 ADL 查找...
namespace notstd{
namespace adl_helper{
template<class T>
decltype(auto) mover(T&&){
using std::move;
// finds both std::move and any move in T's namespace:
return move(std::forward<T>(t));
}
}
namespace adl_blocker {
// will not be found by types in notstd via ADL:
template<class T>
decltype(auto) move(T&& t){
return ::notstd::adl_helper::mover(std::forward<T>(t));
}
}
// import into notstd so it can be named
// via notstd::move:
using namespace ::notstd::adl_blocker;
}
或者类似的东西。
现在friend move
将由 notstd::move(x)
调用代替 std::move
如果是更好的匹配。
事实证明,您不能将其重写为特化,因为您想要更改返回类型。 std::move
定义为:
template< class T >
constexpr typename std::remove_reference<T>::type&& move( T&& t )
在 C++14 中。您的特化必须同意上述返回类型。
我们可以尝试治疗 remove_reference
作为改变返回类型的特征(坏主意!),但这在标准下是非法的,因为这种特化会违反要求 std
穿remove_reference
.
理论上,你可以这样写:
namespace std {
template<>
constexpr typename std::remove_reference<Proxy>::type&& move( Proxy&& t ) {
t.set_move_flag = true;
return static_cast<Proxy&&>(t);
}
template<>
constexpr typename std::remove_reference<Proxy>::type&& move( Proxy const&& t ) {
return static_cast<Proxy const&&>(t);
}
template<>
constexpr typename std::remove_reference<Proxy&>::type&& move( Proxy& t ) {
t.set_move_flag = true;
return static_cast<Proxy&&>(t);
}
template<>
constexpr typename std::remove_reference<Proxy const&>::type&& move( Proxy const& t ) {
return static_cast<Proxy const&&>(t);
}
}
但这太可怕了,如果您尝试使用它,我什至不愿意将它张贴在这里。 (我不知道上面的代码是否包含错误,无论是否是基本错误,但这是我能想到的最接近可能工作的方法。)
关于c++ - 从 std 命名空间专门化函数模板的想法有多糟糕?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42305317/