带有转换运算符移动语义的C++11模板包装器类



我编写了以下类,旨在包装返回值并将其"转发"到调用方不应该关心包装器存在的调用站点。

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语句上移动来确保不复制。

最新更新