ale*_*exc 19 c++ rvalue-reference c++11
想象一下,你有很多重载的方法(在C++ 11之前)看起来像这样:
class MyClass {
public:
void f(const MyBigType& a, int id);
void f(const MyBigType& a, string name);
void f(const MyBigType& a, int b, int c, int d);
// ...
};
Run Code Online (Sandbox Code Playgroud)
这个函数复制了a(MyBigType),所以我想通过提供一个版本f来移动a而不是复制它来添加优化.
我的问题是,现在f重载次数将重复:
class MyClass {
public:
void f(const MyBigType& a, int id);
void f(const MyBigType& a, string name);
void f(const MyBigType& a, int b, int c, int d);
// ...
void f(MyBigType&& a, int id);
void f(MyBigType&& a, string name);
void f(MyBigType&& a, int b, int c, int d);
// ...
};
Run Code Online (Sandbox Code Playgroud)
如果我有更多可以移动的参数,那么提供所有重载是不切实际的.
有人处理过这个问题吗?是否有一个很好的解决方案/模式来解决这个问题?
谢谢!
Rya*_*ing 13
Herb Sutter在cppcon谈话中谈到类似的事情
这可以做,但可能不应该.您可以使用通用引用和模板来获得效果,但是您希望将类型约束为MyBigType可隐式转换为的类型MyBigType.使用一些tmp技巧,你可以这样做:
class MyClass {
public:
template <typename T>
typename std::enable_if<std::is_convertible<T, MyBigType>::value, void>::type
f(T&& a, int id);
};
Run Code Online (Sandbox Code Playgroud)
唯一的模板参数将与参数的实际类型匹配,enable_if返回类型不允许不兼容的类型.我会把它一块一块地拆开
std::is_convertible<T, MyBigType>::value
Run Code Online (Sandbox Code Playgroud)
此编译时表达式将评估trueif是否T可以隐式转换为a MyBigType.例如,如果MyBigTypea std::string和T是一个char*表达式将是真的,但如果T是一个int它将是假的.
typename std::enable_if<..., void>::type // where the ... is the above
Run Code Online (Sandbox Code Playgroud)
void如果is_convertible表达式为真,则此表达式将导致.当它为假时,表达式将格式错误,因此模板将被抛出.
在函数体内你需要使用完美转发,如果你计划复制分配或移动分配,身体就会像
{
this->a_ = std::forward<T>(a);
}
Run Code Online (Sandbox Code Playgroud)
这里有一个coliru活生生的例子了using MyBigType = std::string.正如Herb所说,这个函数不能是虚函数,必须在头文件中实现.与非模板化重载相比,使用错误类型调用获得的错误消息将非常粗糙.
感谢Barry对此建议的评论,为了减少重复,为SFINAE机制创建模板别名可能是个好主意.如果你在课堂上宣布
template <typename T>
using EnableIfIsMyBigType = typename std::enable_if<std::is_convertible<T, MyBigType>::value, void>::type;
Run Code Online (Sandbox Code Playgroud)
然后你可以减少声明
template <typename T>
EnableIfIsMyBigType<T>
f(T&& a, int id);
Run Code Online (Sandbox Code Playgroud)
但是,这假设您的所有重载都具有void返回类型.如果返回类型不同,则可以使用双参数别名
template <typename T, typename R>
using EnableIfIsMyBigType = typename std::enable_if<std::is_convertible<T, MyBigType>::value,R>::type;
Run Code Online (Sandbox Code Playgroud)
然后使用指定的返回类型声明
template <typename T>
EnableIfIsMyBigType<T, void> // void is the return type
f(T&& a, int id);
Run Code Online (Sandbox Code Playgroud)
稍慢的选项是按值获取参数.如果你这样做
class MyClass {
public:
void f(MyBigType a, int id) {
this->a_ = std::move(a); // move assignment
}
};
Run Code Online (Sandbox Code Playgroud)
在f传递左值的情况下,它将a从其参数复制构造,然后将其分配给this->a_.在f传递rvalue 的情况下,它将a从参数移动构造,然后移动assign.这种行为的实例就在这里.请注意,我使用-fno-elide-constructors没有该标志的rvalue情况除了移动构造并且仅发生移动分配.
如果对象移动成本很高(std::array例如),这种方法将明显慢于超级优化的第一版.另外,请考虑观看Chris Drew在评论中链接的Herb的演讲中的这部分内容,以了解它何时可能比使用引用更慢.如果您有Scott Meyers的Effective Modern C++副本,他将讨论第41项中的起伏.
您可以执行以下操作.
class MyClass {
public:
void f(MyBigType a, int id) { this->a = std::move(a); /*...*/ }
void f(MyBigType a, string name);
void f(MyBigType a, int b, int c, int d);
// ...
};
Run Code Online (Sandbox Code Playgroud)
你只需要一个额外的move(可以优化).
我的第一个想法是你应该改变参数来传递值.这包括复制的现有需要,除了复制发生在调用点而不是在函数中显式.它也允许通过移动结构中的移入能够上下文(或者未命名的临时或通过使用所创建的参数std::move).