【问题标题】:why use references for mutable variables in c++为什么在 C++ 中使用可变变量的引用
【发布时间】:2019-08-12 15:32:27
【问题描述】:

我是 C++ 新手。我遇到了一些代码并感到困惑

vector<int> vec{3,1,4,1,5};
vector<int> &vecRef = vec;
auto vecCopy = vecRef; // makes copy of vec
auto &vecRef2 = vecRef; // reference

我读到了 C++ 中引用类型的用法,我明白为什么它对不可变类型有用。但是对于像向量这样的可变类型,vector vecCopy = vec 和vector& vecRef = rec 有什么区别呢?他们不是都是 vec 的别名吗?

【问题讨论】:

  • 你为什么认为vector vecCopy = vec; 是别名?您自己的示例明确表示您期望副本,而不是别名。
  • 正如您在代码中所说,vecCopy 是一个副本。您是要复制数据,还是只是用不同的名称引用它?
  • 我认为 OP 可能将vec 视为“向量句柄”,在这种情况下我可以理解这种混淆。从这个角度来看,说vecCopy 似乎是在复制句柄。重要的是要了解 C++ 中的对象,如 vec 对象,而不是对象的句柄。复制它意味着创建一个新对象,该对象具有复制时原始值的副本。它不是为同一对象创建新句柄。这就是对象和对对象的引用之间的区别。似乎 OP 可能来自 Java 背景或类似背景。
  • mutable 关键字在 C++ 中有特定的含义;您可能还没有在书中遇到过它,但它使标题令人困惑。
  • @Kathryn C++ 有点不同,默认情况下它具有值语义,变量是对象,而不是对对象的引用。

标签: c++ c++11 reference copy auto


【解决方案1】:

但是对于像向量这样的可变类型,它们之间有什么区别 向量 vecCopy = vec 和向量& vecRef = rec?他们不是都是别名吗 到vec?

没有。一个是整个向量的副本。另一个是相同的引用。

您的示例代码是人为设计的。我想不出你为什么要这样做:

vector<int> vec{3,1,4,1,5};
vector<int> &vecRef = vec;

始终通过引用传递变量。但我无法想象我为什么要像这样引用局部变量的原因,只是为了说明一个引用而不是副本的示例。

所以:vecCopy 是一个完整的不同向量,有自己的内容。在您的代码末尾,它的内容与 vec 相同,但在此之后,您可以添加一个或另一个,它们开始发散。 vecRef 是对完全相同数据的引用。如果您将它们视为(在底层)指针,它们指向同一个对象。

【讨论】:

    【解决方案2】:

    引用和值之间的差异。

    C++ 的一个特点是它区分引用和值。许多其他语言不这样做。假设你有一个向量:

    std::vector<int> v1 = {1, 2, 3};
    

    创建此向量的深层副本非常简单:

    auto copy_of_v1 = v1;
    

    我们可以通过更改copy_of_v1来证明这一点:

    std::cout << (v1 == copy_of_v1) << '\n'; // Prints 1, for true
    
    copy_of_v1[1] = 20; // copy_of_v1 == {1, 20, 3} now
    
    std::cout << (v1 == copy_of_v1) << '\n'; // Prints 0, for false
    

    参考用例。

    引用具有三个主要用例: - 通过存储/使用参考来避免复制 - 从函数中获取附加信息(通过向其传递引用,并让它修改引用) - 编写数据结构/容器类

    我们已经看到了第一个案例,让我们看看另外两个案例。

    使用引用来编写修改其输入的函数。假设您想使用+= 添加将元素附加到向量的功能。运算符是一个函数,因此如果要修改向量,则需要对其进行引用:

    // We take a reference to the vector, and return the same reference
    template<class T>
    std::vector<T>& operator +=(std::vector<T>& vect, T const& thing) {
        vect.push_back(thing); 
        return vect;
    }
    

    这允许我们将元素附加到向量中,就像它是一个字符串一样:

    int main() {
        std::vector<int> a;
    
        ((a += 1) += 2) += 3; // Appends 1, then 2, then 3
    
        for(int i : a) {
            std::cout << i << '\n'; 
        }
    }
    

    如果我们不通过引用获取向量,函数将无法更改它。这意味着我们将无法附加任何内容。

    使用引用来编写容器。

    引用使在 C++ 中编写可变容器变得容易。当我们想要提供对容器中某些内容的访问时,我们只需返回对它的引用。这提供了对元素的直接访问,甚至是基元。

    template<class T>
    class MyArray {
        std::unique_ptr<T[]> array;
        size_t count; 
       public:
        T* data() {
           return array.get();
        }
        T const* data() {
           return array.get();
        }
        MyArray() = default;         // Default constructor
        MyArray(size_t count)        // Constructs array with given size
          : array(new T[count])
          , count(count) {}
        MyArray(MyArray const& m)    // Copy constructor
          : MyArray(count) {
            std::copy_n(m.data(), count, data();
        }
        MyArray(MyArray&&) = default;// Move constructor
    
        // By returning a reference, we can access elements directly 
        T& operator[](size_t index) {
           return array[index]; 
        }
    };
    

    现在,当使用MyArray 时,我们可以直接更改和修改元素,即使它们是图元:

    MyArray<int> m(10); // Create with 10 elements
    
    m[0] = 1;           // Modify elements directly
    
    m[0]++;             // Use things like ++ directly
    

    【讨论】:

      【解决方案3】:

      在 c++ 中使用引用与仅使用对象本身的名称相同。因此,您可能会将引用视为别名。

      vector<int> vec = {1, 2, 3};
      
      vector<int>& vecRef = vec;
      
      cout << vec.size()    << '\n'; // Prints '3'
      cout << vecRef.size() << '\n'; // Also prints '3'
      

      值得注意的是,没有人真正使用引用来简单地为现有对象使用另一个名称。 它们主要用于代替指针来传递对象而不复制它们。

      【讨论】:

      • 既然您已经指出了这一点,我就明白为什么它会引起混乱了。我已经编辑了答案。
      • “没有人真正使用引用来简单地为现有对象命名”——我经常这样做。当您需要为嵌套在其他东西中的东西取一个更好的名称时,它非常有用。例如,Foo&amp; o = things[i].bar.baz; o.do_something(); o.do_somethingelse(); do_anotherthing(o); o = something();
      【解决方案4】:

      C++ 默认使用值语义。对象是值,除非您明确将它们声明为引用。所以:

      auto vecCopy = vecRef;
      

      将创建一个名为vecCopy 的值对象,其中将包含vec 的深层副本,因为vecRefvec 的别名。在 Python 中,这将大致转换为:

      import copy
      
      vec = [3, 1, 4, 1, 5]
      vecCopy = copy.deepcopy(vec)
      

      请注意,它只是“大致”转换为那个。复制的执行方式取决于对象的类型。对于内置类型(例如intchar),它是它们包含的数据的直接副本。对于类类型,它调用复制构造函数或复制赋值运算符(在您的示例代码中,它是复制构造函数。)因此,实际执行复制取决于这些特殊成员函数。默认的复制构造函数和赋值运算符将复制每个类成员,如果它有一个成员,则依次调用该成员的复制 ctor 或赋值运算符等,直到所有内容都被复制。

      C++ 中的值语义允许编译器对使用引用语义时难以执行的某些代码生成优化。显然,如果你复制大对象,值的性能优势将被复制数据的性能成本抵消。在这些情况下,您将使用引用。显然,如果您需要修改传递的对象而不是它的副本,则需要使用引用。

      通常,除非有使用引用的理由,否则首选值语义。例如,一个函数应该按值取参数,除非传递的参数需要修改,或者太大。

      此外,使用引用可能会增加遇到未定义行为的风险(指针会产生相同的风险。)例如,您可以使用悬空引用(例如,引用已破坏对象的引用。)但您不能有悬空值。

      引用还会降低您推理程序中发生的事情的能力,因为对象可以通过非本地代码的引用进行修改。

      无论如何,这是一个相当大的主题。随着您使用该语言并获得更多经验,事情开始变得更加清晰。如果有一个非常一般的经验法则可以摆脱所有这些:使用值,除非有理由不这样做(主要是对象大小,需要传递的函数参数的可变性,或者运行时多态类,因为那些要求在需要多态访问时通过引用或指针进行访问。)

      您还可以找到有关该主题的初学者文章和讨论。这是一个帮助您入门的方法:

      https://www.youtube.com/watch?v=PkyD1iv3ATU

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-02-15
        • 2016-03-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-08
        • 2010-10-31
        相关资源
        最近更新 更多