我编写了以下类,旨在包装返回值并将其"转发"到调用方不应该关心包装器存在的调用站点。
template<typename T>
class Wrapper
{
public:
Wrapper(T&& t) : t(std::forward<T>(t)) {};
Wrapper(const Wrapper& rhs) : t(std::move(rhs.t)) {};
~Wrapper() {};
operator T() { /* I hook here, e.g. logging, asserting */ return std::move(t); };
private:
T t;
};
Wrapper<std::vector<int>> foo()
{
return std::vector<int>();
}
void bar()
{
std::vector<int> v = foo();
}
此外,一开始,我最初写
operator T() const { return std::move(t); };
在这样做的时候,我注意到步骤调试将我发送到std::vector(const vector&)
副本构造函数,并且我希望避免副本。
有人能复习一下吗?当我写Wrapper(const Wrapper& rhs)
时,我也有疑问,但在Mac上用Clang进行的步骤调试表明它确实被使用了(而MSVC 2013似乎甚至在调试中也应用了RVO)。
提前谢谢。
PS:有趣的是,在MSVC 2013中将__forceinline
添加到operator T()
会触发C4714(默认为关闭)。这让我认为,即使进行了移动,生成的代码也没有我希望的那么优化。
move构造函数接受其参数(从值中移出)为非const
,因为从值中移动可能会修改该值。
如果您将operator T
声明为const
,则这种情况将不再发生:Wrapper::t
现在是const
,因此无法从中移动,只能复制。
顺便说一句,Wrapper
的复制构造函数也是如此。你真的需要这个吗?对于Wrapper
,默认的复制和移动构造函数应该做得很好。从根本上讲,您不能强迫编译器执行RVO,尽管可以将其作为优化。您可以*不*依赖它来获得正确性(这意味着,即使在不执行RVO的假设下,您的代码也必须是正确的)。
Wrapper(T&& t) : t(std::forward<T>(t)) {};
在这里,您将两个不同的变量命名为t
,最好以不同的方式命名。然后没有理由在这里应用std::forward
,您已经有了一个右值引用。
Wrapper(const Wrapper& rhs) : t(std::move(rhs.t)) {};
在这里,您尝试移出const
值,因为rhs
是常量,rhs.t
也是常量。不起作用,你无论如何都会得到一份。
整个练习似乎毫无意义。如果包装有move构造函数的类型,编译器无论如何都会使用它来返回。如果您在没有移动语义的情况下包装类型,这将不起作用,并且您将面临额外副本的风险。那么这里的用例是什么呢?
首先,RVO只在这种情况下发生:
vector<int> foo() {return vector<int>();}
为了说服你,这个例子:
struct A
{
A(int v=0) : v(v) {std::cout << "ctor" << std::endl;}
A(A const& rhs) : v(rhs.v) {std::cout << "cctor" << std::endl;}
A(A&& rhs) : v(rhs.v) {std::cout << "mctor" << std::endl;}
int v;
};
A foo() {return A(2);}
int main()
{
A a = treat(foo());
}
退货:
$> ctor
您甚至不需要经过move构造函数。但是,包装时:
template<typename T>
struct ensure_fwd
{
T t;
ensure_fwd(T&& t) : t(std::forward<T>(t)) {std::cout << "check" << std::endl;}
operator T(){return std::move(t);}
};
ensure_fwd<A> foo()
{
return A();
}
这会编译并得到以下输出:
ctor
mctor
check
mctor
请注意,这不是一个很好的做法:你松开RVO,可以调用两次a.的move构造函数
当你在你的例子中使用vector<int>
时,我想你想声明你的向量,做一些push_back,然后返回它
ensure_fwd<A> foo() {A a; a.v = 2; return a;}
你得到了一个编译器错误(我个人得到的是:No viable conversion from 'A' to ensure_fwd<A>
。那是因为我们忘记移动我们的结果:
ensure_fwd<A> foo() {A a; a.v = 2; return std::move(a);}
这样编译后,您将获得与以前相同的输出。同样,ensure_fwd不是很有用,您对A move ctor进行了两次调用,而不是一次。你应该简单地选择:
A foo() {A a; a.v = 2; return std::move(a);}
这对向量也是一样的,您不能忘记std::move
,但不能使用包装器从函数外部进行移动。您只能检查它不复制,但这会禁用RVO,并通过在return语句上移动来确保不复制。