尝试更好地理解std::forward和std::move

9
我正在编写一个基本的类模板,它需要两种参数类型。该类的思想是将一种类型作为const ref输入,将另一种类型作为ref输入。该类的功能是将类型A转换为类型B,其中创建的对象最终将成为b。我希望在这个类模板中使用perfect-forwardingmove semantics
目前,这是我的当前类,只有基本类型,但计划通过可变构造扩展到任何2种类型。
#ifndef CONVERTER_H
#define CONVERTER_H

#include <utility>

template<class From, class To>
class Converter {
private:
    From in_;
    To   out_;

public:
    // Would like for From in to be a const (non modifiable) object
    // passed in by perfect forwarding or move semantics and for 
    // To out to be returned by reference with perfect forwarding 
    // or move semantics. Possible Constructor Declarations - Definitions

    // Using std::move
    Converter( From&& in, To&& out ) :
        in_{ std::move( in ) },
        out_{ std::move( out ) } 
    {
        // Code to convert in to out
    }

    // Or using std::forward
    Converter( From&& in, To&& out ) :
        in_{ std::forward<From>( in ) },
        out_{ std::forward<To>( out ) } {

        // Code to convert in to out.
     }        

    // Pseudo operator()... 
    To operator()() {
        return out_;
    }
};

#endif // !CONVERTER_H

无论我如何使用 std::movestd::forward 来声明上面的构造函数,这个类都可以编译通过。现在当我包含它并尝试调用其构造函数来实例化一个对象时... 如果我这样做:
int i = 10;
float f = 0;  
Converter<int, float> converter( i, f );

这在Visual Studio 2017中会导致编译错误,无论是哪种情况。
1>------ Build started: Project: ExceptionManager, Configuration: Debug Win32 ------
1>main.cpp
1>c:\users\skilz80\documents\visual studio 2017\projects\exceptionmanager\exceptionmanager\main.cpp(54): error C2664: 'Converter<unsigned int,float>::Converter(Converter<unsigned int,float> &&)': cannot convert argument 1 from 'unsigned int' to 'unsigned int &&'
1>c:\users\skilz80\documents\visual studio 2017\projects\exceptionmanager\exceptionmanager\main.cpp(54): note: You cannot bind an lvalue to an rvalue reference
1>Done building project "ExceptionManager.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

这是可以理解的 {无法将左值绑定到右值引用}。


然而,如果我尝试像这样使用构造函数:

int i = 10;
float f = 0;
Converter<int,float> converter( std::move( i ), std::move( f ) );

// Or
Converter<int,float> converter( std::forward<int>( i ), std::forward<float>( f ) );

无论在类内使用std::move(...)还是std::forward<T>(...),它都可以编译和构建。


据我所知,std::move(...)std::forward<T>(...)几乎是可以互换的,除了std::forward<T>(...)有一个额外的转换。


现在,由于这个类只展示基本类型,因此使用std::move似乎更可行,但是我可能最终想要使用更复杂的类型,因此我想提前考虑这一点,因此我倾向于使用std::forward<T>进行完美转发。


在此情况下,为了完成这个类,有三个相关的问题需要解答。

  • 如果我在类的构造函数成员初始化列表中使用std::movestd::forward,那么在实例化模板类对象时为什么还要再次使用它们?这不会被认为是多余的吗?如果是,那么构造函数应该如何设计,以便用户在调用此构造函数时不必使用std::move()std::forward<T>()
  • 在这个上下文中,将A转换为B的最普遍和类型安全的方法是什么?
  • 一旦上述两个问题得到明确的回答,那么在这个上下文中,对于上述标准类或其他类似类型的实现operator()()将是什么样子?

关于上述三个相关问题的解答,我的最终想法是,在设计过程的某个时刻,我曾考虑使用std::any及其相关函数作为其实现过程的一部分。我不知道std::any是否可以在这种情况下使用,如果可以,它应该如何使用?


编辑

以下可能是这个类未来的一些合理用途:

vector<int> vecFrom{1,2,3, ...};
set<int>    setTo;

Converter<vector<int>, set<int>> converter( vecFrom, setTo );

或者在展开之后……
vector<int>     vecIntFrom{1,2,3, ...};
vector<string>  vecStringFrom{ "a", "b", "c", ... };
map<int,string> mapTo;
Converter<vector<int>, vector<string>, map<int,string> converter( vecIntFrom, vecStringFrom, mapTo );

3
只有在模板类型推导起作用时,T&& 才是一个转发引用。从您的代码 Converter<int, float> converter(i, f) 可以看出,没有进行类型推导,因此这两个参数最终成为右值引用。 - Mário Feroldi
3
std::move 是这种情况下更好的选择。std::forward 的目的是与推导出的模板参数一起使用。 - M.M
@M.M. 嗯,好的,这似乎有点讲得通;只要这个类的模板声明没有模板参数,那么forward就是过度使用了,而move则是合适的...我想我开始理解两者之间的总体差异概念了。 - Francis Cugler
1
它们在编译时生成相同的内容(当T不是左值引用时),问题在于记录意图。 - M.M
1
阅读Peter Gottschling的《Discovering Modern C++》和Scott Meyers的《Effective Modern C++》这样的书籍是一个好主意。在我看来,这是了解现代C++的最佳途径之一。 - Phil1970
显示剩余13条评论
1个回答

3

这里有很多问题似乎没有简明的答案,但有一个问题很突出:

“std::move和std::forward有什么区别?”

std::move用于将左值引用转换为右值引用,通常用于将一个左值持有的资源转移到另一个左值。

std::forward用于区分左值和右值引用,通常在被推断为左值的类型为右值引用的参数中使用。

总之:如果您想基于传递对象时引用的类型进行分支,请使用std::forward。如果您只想通过转换为右值来窃取左值的资源,请使用std::move。

欲了解更多详情,请参考以下链接:http://thbecker.net/articles/rvalue_references/section_01.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接