【问题标题】:C++11 move(x) actually means static_cast<X&&>(x)? [duplicate]C++11 move(x)实际上是指static_cast<X&&>(x)? [复制]
【发布时间】:2013-12-30 07:13:24
【问题描述】:

刚刚阅读 Stroustrup 的 C++ Programming Language 第 4 版,在第 7 章中他说:

move(x) 表示 static_cast&lt;X&amp;&amp;&gt;(x) 其中 X 是 x 的类型

因为 move(x) 不移动 x(它只是产生一个右值引用 to x) 如果 move() 被称为 rval() 会更好

我的问题是,如果move() 只是将变量转换为 rval,那么实现对变量的引用“移动”(通过更新指针)的实际机制是什么?

我认为move() 就像一个移动构造函数,除了客户端可以使用move() 来强制编译器??

【问题讨论】:

  • 移动构造函数进行移动(并且由于它们接受右值引用,它们被视为“移动构造函数”),并且std::move 显式地将某些东西设为右值,从而可以使用构造函数。

标签: c++ c++11 move move-semantics


【解决方案1】:

实现对变量的引用“移动”(通过更新指针)的实际机制是什么??

将它传递给一个接受 rvalue 引用的函数(或构造函数),并从该引用中移动值。没有强制转换,变量不能绑定到 rvalue 引用,因此不能传递给这样的函数 - 这可以防止变量被意外移动。

我以为move() 就像一个移动构造函数,除了客户端可以使用move() 来强制编译??

没有;它用于将 lvalue 转换为 rvalue 以便将其传递给需要 rvalue 引用的移动构造函数(或其他移动函数) .

typedef std::unique_ptr<int> noncopyable;  // Example of a noncopyable type
noncopyable x;
noncopyable y(x); // Error: no copy constructor, and can't implicitly move from x
noncopyable z(std::move(x)); // OK: convert to rvalue, then use move constructor

【讨论】:

    【解决方案2】:

    当您调用move 时,您只是在告诉“嘿,我想移动这个对象”。而当构造函数接受右值引用时,它会将其理解为“嗯,有人想要我将数据从这个对象移动到我自己。所以,好的,我会这样做”

    std::move 不会移动或更改对象,它只是将其“标记”“准备移动”。只有接受右值引用的函数才能实现移动实际对象。

    这是一个例子,描述了上面的文字:

    #include <iostream>
    #include <utility>
    
    class Foo
    {
    public:
        Foo(std::size_t n): _array(new int[n])
        {
    
        }
    
        Foo(Foo&& foo): _array(foo._array)
        {
            // Hmm, someone tells, that this object is no longer needed
            // I will move it into myself
            foo._array = nullptr;
        }
    
        ~Foo()
        {
            delete[] _array;
        }
    
    private:
        int* _array;
    };
    
    int main()
    {
        Foo f1(5);
    
        // Hey, constructor, I want you move this object, please
        Foo f2(std::move(f1));
    
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      Going Native 2013 一样,Scott Meyers 发表了有关 C++ 11 特性的演讲,包括 move

      std::move 本质上所做的是“无条件强制转换为右值”。

      我的问题是,如果 move() 只是将变量转换为 rval,那么实现对变量的引用“移动”(通过更新指针)的实际机制是什么?

      move 进行类型转换,因此编译器将知道使用哪个ctor。实际的move 操作由移动ctor 完成。您可以将其视为函数重载。 (ctor 使用右值参数类型重载。)

      【讨论】:

        【解决方案4】:

        右值通常是临时值,在创建后立即被丢弃和销毁(有一些例外)。 std::string&amp;&amp; 是对std::string 的引用,它 绑定到右值。在 C++11 之前,临时对象只能绑定到 std::string const&amp; -- 在 C++11 之后,它们还绑定到 std::string&amp;&amp;

        std::string&amp;&amp; 类型的变量的行为很像沼泽标准引用。 std::string&amp;&amp;std::string&amp; 变量的不同之处几乎仅在于函数签名和初始化的绑定。另一种不同的方式是当您 decltype 引用时。所有其他用途均未更改。

        另一方面,如果一个函数返回std::string&amp;&amp;,它与返回std::string&amp;非常不同,因为第二种可以绑定到std::string&amp;&amp;是返回std::string&amp;&amp;的函数的返回值。

        std::move 是生成此类函数的最常用方法。从某种意义上说,它欺骗它所处的上下文并告诉它“我是一个临时的,随我做你想做的”。所以std::move 引用了某个东西,并进行了一次强制转换,使它假装是一个临时的——也就是右值。

        移动构造函数和移动赋值以及其他移动感知函数采用右值引用来了解它们传递的数据何时是“临时”数据,它们在使用时可能会在一定程度上“损坏”。这非常有用,因为许多类型(从容器到std::function,到任何使用pImpl 模式的东西,再到不可复制的资源)都可以更容易地移动它们的内部状态而不是复制它。这样的移动会改变源对象的状态:但是因为函数被告知它是临时数据,所以这不是不礼貌的。

        所以move 不是在std::move 中发生,而是在理解std::move 的返回值意味着允许以某种破坏性方式修改数据的函数中,如果这有帮助的话。

        获得右值或源对象是“临时数据”的指示的其他方法是,当您有一个真正的临时对象(作为其他函数的返回创建的匿名对象,或使用函数创建的对象时) -style 构造函数语法),或者当您从带有 return local_variable; 形式的语句的函数返回时。在这两种情况下,数据都绑定到右值引用。

        简短的版本是std::move不移动,std::forward不转发,它只是表示此时允许这样的动作,并让被调用的函数/构造函数决定如何处理这些信息。

        【讨论】:

          【解决方案5】:

          来自http://en.cppreference.com/w/cpp/utility/move

          std::move 获得对其参数的右值引用并将其转换 到一个 xvalue。

          接收此类 xvalue 的代码有机会 通过将数据移出参数来优化不必要的开销, 使其处于有效但未指定的状态。

          返回值

          static_cast<typename std::remove_reference<T>::type&&>(t)
          

          你可以看到 move 只是static_cast

          通过在对象上调用std::move 并没有真正做任何有用的事情,但是它告诉我们可以将返回值修改为“有效但未指定的状态”

          【讨论】:

            【解决方案6】:

            我认为 move() 就像一个移动构造函数,除了客户端可以 使用 move() 强制编译??

            通过本质上将类型转换为右值类型,这允许编译器调用移动构造函数而不是复制构造函数。

            【讨论】:

              【解决方案7】:

              std::move 等价于static_cast&lt;std::string&amp;&amp;&gt;(x)

              在标准中是这样定义的:

              template <class T>
              constexpr remove_reference_t<T>&& move(T&&) noexcept;
              

              【讨论】:

                【解决方案8】:

                作为其他答案的补充,一个示例可以帮助您更好地理解右值引用的工作原理。查看以下模拟右值引用的代码:

                #include <iostream> 
                #include <memory>
                
                template <class T>
                 struct rvalue_ref
                 {
                    rvalue_ref(T& obj) : obj_ptr{std::addressof(obj)} {} 
                
                    T* operator->() //For simplicity, we'll use the reference as a pointer.
                    { return obj_ptr; }
                
                    T* obj_ptr; 
                 };
                
                template <class T>
                 rvalue_ref<T> move(T& obj)
                 {
                    return rvalue_ref<T>(obj);
                 }
                
                
                template <class T>
                 struct myvector
                 {
                    myvector(unsigned sz) : data{new T[sz]} {}
                
                    myvector(rvalue_ref<myvector> other) //Move constructor
                    {
                        this->data = other->data;
                        other->data = nullptr;
                    }
                
                    ~myvector()
                    {
                       delete[] data;
                    }
                
                    T* data; 
                 }; 
                
                int main()
                {
                    myvector<int> vec(5); //vector of five integers
                
                    std::cout << vec.data << '\n'; //Print address of data
                
                    myvector<int> vec2 = move(vec); //Move data from vec to vec2
                
                    std::cout << vec.data << '\n'; //Prints zero
                
                    //Prints address of moved data (same as first output line)
                    std::cout << vec2.data << '\n'; 
                }
                

                正如我们所见,“move”只会生成正确的别名,以向编译器指示要使用哪个构造函数重载。这个实现和真正的右值引用之间的区别当然是转换为右值引用的开销为零,因为它只是一个编译器指令。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2020-02-28
                  • 2018-06-13
                  • 2019-03-27
                  • 2010-09-11
                  • 2015-01-19
                  • 1970-01-01
                  • 2023-03-04
                  • 2013-04-05
                  相关资源
                  最近更新 更多