C++Rvalue引用和移动语义

本文关键字:语义 移动 引用 C++Rvalue | 更新日期: 2023-09-27 18:26:21

C++03存在可能隐式发生的不必要复制的问题。为此,C++11引入了rvalue referencesmove semantics。现在我的问题是,这种不必要的复制问题是否也存在于C#和java等语言中,或者它只是一个C++问题?换句话说,与C#或Java相比,rvalue references是否使C++11更加高效?

就C#而言(允许运算符重载),假设我们有一个数学向量类,我们这样使用它。

vector_a = vector_b + vector_c;

编译器肯定会将vector_b + vector_c转换为某个临时对象(让我们称之为vector_tmp)。

现在我不认为C#可以区分像vector_tmp这样的临时右值和像vector_b这样的左值,所以我们无论如何都必须将数据复制到vector_a,在C++11中使用rvalue referencesmove semantics可以很容易地避免这种情况。

C++Rvalue引用和移动语义

C#和Java中的类引用具有C++中shared_ptrs的一些属性。然而,右值引用和移动语义更多地与临时值类型相关,但与C++值类型相比,C#中的值类型是非常不灵活的,根据我自己的C#经验,大多数时候你会得到类,而不是结构。

因此,我的假设是,Java和C#都不会从这些新的C++功能中获得太多利润,这些功能让代码可以安全地假设某个东西是否是临时的,而不是复制,让它只是窃取内容。

是的,C#和java中存在不必要的复制操作。

与C#或Java相比,右值引用是否使C++11更加高效?

答案是肯定的

因为Java和C#中的类使用引用语义,所以在这些语言中从来没有对象的任何隐式副本。移动语义解决的问题在Java和C#中并不存在,也从未存在过。

我认为这可能发生在Java中。请参阅下面的add和add_to操作。add创建一个result对象来保存矩阵add操作的结果,而add_to只是将rhs添加到其中。

class Matrix   {
   public static final int w = 2;
   public static final int h = 2;
   public float [] data;
   Matrix(float v)
   {
       data = new float[w*h];
       for(int i=0; i<w*h; ++i)
           { data[i] = v; }
   }
   // Creates a new Matrix by adding this and rhs 
   public Matrix add(Matrix rhs)
   {
       Main result = new Main(0.0f);
       for(int i=0; i<w*h; ++i)
           { result.data[i] = this.data[i] + rhs.data[i]; }
       return result;
   }
   // Just adds the values in rhs to this
   public Main add_to(Main rhs)
   {
       for(int i=0; i<w*h; ++i)
           { this.data[i] += rhs.data[i]; }
       return this;    
   }
    public static void main(String [] args)
    {
       Matrix m = new Matrix(0.0f);
       Matrix n = new Matrix(1.0f);
       Matrix o = new Matrix(1.0f);
       // Chaining these ops would modify m
       // Matrix result = m.add_to(n).subtract_from(o);      
       m.add_to(n);         // Adds n to m
       m.subtract_from(o);  // Subtract o from n
       // Can chain ops without modifying m,
       // but temps created to hold results from each step
       Matrix result = m.add(n).subtract(o);
    }
}

因此,我认为这取决于你用你的类为用户提供什么样的功能。

这个问题经常出现。我想保留一个其他人无法修改的对象的唯一副本。我该怎么做?

  1. 对别人给我的任何东西都做一个深度复制?这是可行的,但效率不高
  2. 让人们给我一个新的对象,而不是保留副本?如果你勇敢的话,速度会更快。错误可能来自于一段完全无关的代码,该代码在数小时后修改了对象
  3. C++风格:将输入中的所有项目移动到我自己的新对象中。如果调用者不小心再次尝试使用该对象,他将立即看到问题
  4. 有时C#只读集合会有所帮助。但根据我的经验,这充其量只是一种痛苦

以下是我所说的:

class LongLivedObject
{
    private Dictionary <string, string> _settings;
    public LongLivedObject(Dictionary <string, string> settings)
    {   // In C# this always duplicates the data structure and takes O(n) time.
        // C++ will automatically try to decide if it could do a swap instead.
        // C++ always lets you explicitly say you want to do the swap.
        _settings = new Dictionary <string, string>(settings);
    }
}

这个问题是Clojure和其他函数式语言的核心!

总之,是的,我经常希望我在C#中拥有C++11风格的数据结构和操作。

您可以尝试模拟移动语义。例如,在Trade Ideas Philip的示例中,您可以传递自定义MovableDictionary而不是Dictionary:

public class MovableDictionary<K, V> // : IDictionary<K, V>, IReadOnlyDictionary<K, V>...
{
    private Dictionary<K, V> _map;
    // Implement all Dictionary<T>'s methods by calling Map's ones.
    public Dictionary<K, V> Move()
    {
        var result = Map;
        _map = null;
        return result;
    }
    private Dictionary<K, V> Map
    {
        get
        {
            if (_map == null)
                _map = new Dictionary<K, V>();
            return _map;
        }
    }
}