为什么按值传递时复制c ++向量内部元素/数组

时间:2019-03-22 17:25:26

标签: c++ arrays vector

为什么向量通过值传递时会复制向量的内部元素?

#include<vector>
using namespace std;

// this func won't modify v[2]. 
// Meaning v[2] (and the whole inner array) was copied 
// when v is passed to the func?
void modify(vector<int> v) {
    v[2] = 100;
}

// this func modify v[2]
void modify(vector<int>& v) {
    v[2] = 100;
}

int main() {
    vector<int> v = {1,2,3,4,5,6};

    // still same
    modify(v);

    // modified
    modified2(v);
}

我发现奇怪的是,当按值传递向量时,会复制向量的实际内容。我想象到std :: vector实现必须具有一个指针字段,该字段映射到实际数组所在的堆上的地址。因此,即使通过值传递向量,地址也应保持不变,指向相同的内容。像这样:

#include<iostream>

using namespace std;

// a dummy wrapper of an array
// trying to mock vector<int>
class vector_int {
    public:
    int* inner_array; // the actual array 
    vector_int(int *a) {
        inner_array = a;
    }
    int* at(int pos) {
        return inner_array+pos;
    }
};

// this passes the "mocked vector" by value
// but 'inner_array' is not copied
void modify(vector_int v) {
    *(v.at(2)) = 10;
}

int main() {
    int* a = new int[3] {1,2,3};
    vector_int v = vector_int(a);
    modify(v); // v[2] is modified
}

关于std :: vector实现的这种假设正确吗?是什么使向量内容按值传递时被复制?


编辑

由于要改变igel的回答和UnholySheep的评论,我弄清楚了std :: vector具有 value sementics 的原因(或者为什么复制了内部数组)。

如果在类定义中显式定义了复制构造函数类,则当在函数调用中传递变量时,复制构造函数将确定如何复制结构/类实例。因此,我可以为我的vector_int定义一个复制构造函数,在其中复制整个inner_array,例如

#include<iostream>

using namespace std;

class vector_int {
    public:
    int* inner_array;
    int len;
    vector_int(int *a, int len) {
        inner_array = a;
        this->len = len;
    }
    int* at(int pos) {
        return inner_array+pos;
    }
    // this is the copy constructor
    vector_int(const vector_int &v2) {
        inner_array = new int;
        for (int i =0; i < v2.len; i++) {
            *(inner_array+i) = *(v2.inner_array+i);
        }
    } 
};

// Yay, the vector_int's inner_array is copied
// when this function is called
// and no modification of the original vector is done
void modify(vector_int v) {
    *(v.at(2)) = 10;
}

int main() {
    int* a = new int[3] {1,2,3};
    vector_int v = vector_int(a,3);
    // 
    modify(v);
}

我在本地计算机(g ++ Apple LLVM版本10.0.0)上检查了stdlib实现的源代码。 std :: vector定义了一个如下所示的复制构造函数

template <class _Tp, class _Allocator>
vector<_Tp, _Allocator>::vector(const vector& __x)
    : __base(__alloc_traits::select_on_container_copy_construction(__x.__alloc()))
{
    size_type __n = __x.size();
    if (__n > 0)
    {
        allocate(__n);
        __construct_at_end(__x.__begin_, __x.__end_, __n);
    }
}

看起来它为实际复制的数组做malloc +复制该数组。

2 个答案:

答案 0 :(得分:3)

C ++允许类类型为其创建,复制,移动和销毁的含义提供自己的代码,并且隐式调用该代码,而无需进行任何明显的函数调用。这就是所谓的值语义,这是C ++在其他语言求助于create_foo(foo)foo.clone()destroy_foo(foo)foo.dispose()之类的语言时所使用的语言。

每个类都可以定义以下special member functions

  • 构造函数,用于将对象置于有效的初始状态
  • 破坏者,负责任地清理
  • 复制构造函数,用于创建与另一个对象重复的新对象
  • 移动构造函数,用于通过传输另一个对象的数据来创建新对象
  • 复制分配运算符,用于将对象复制到现有对象中
  • 移动分配运算符,用于在两个现有对象之间传输数据

这些都是您可以定义以执行所需操作的所有功能。但是它们被称为 implicitly ,这意味着此类的用户在他们的代码中看不到这些函数调用,他们希望它们做可预测的事情。您应该遵循Rule of Three/Five/Zero来确保类的行为可预测。

当然,您还已经知道其他用于共享数据的工具,例如传递引用。

标准库中的许多类都使用这些特殊的成员函数来实现特殊的行为,这些行为非常有用并有助于用户编写安全正确的代码。例如:

  • std::vector在复制时将始终具有相同的元素,尽管所包含的基础数组和对象将是分开的。
  • std::unique_ptr包装仅拥有一个所有者的资源。要强制执行此操作,不能复制
  • std::shared_ptr包装具有许多所有者的资源。目前尚不清楚何时清理此类资源,因此复制shared_ptr会执行自动引用计数,并且仅在最后一个所有者完成后才清理资源。

答案 1 :(得分:1)

这是因为vector具有 value 语义:当您复制它时,会得到所有元素的真实副本。