Почему c ++ векторные внутренние элементы / массив копируются при передаче по значению - PullRequest
1 голос
/ 22 марта 2019

Почему внутренние элементы вектора копируются, когда вектор передается по значению?

#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?Что делает векторное содержимое копируемым при передаче по значению?


EDIT

Благодаря ответу alter igel и комментарию UnholySheep я выяснил причину, по которой std :: vector имеет значение 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);
}

Я проверил исходный код реализации stdlib на моем локальном компьютере (g ++ Apple LLVMверсия 10.0.0).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 ]

3 голосов
/ 22 марта 2019

C ++ позволяет типам классов предоставлять свой собственный код для того, что означает создание, копирование, перемещение и уничтожение, и этот код вызывается неявно, без каких-либо явных вызовов функций.Это называется семантикой значений, и это то, что C ++ использует, когда другие языки прибегают к таким вещам, как create_foo(foo), foo.clone(), destroy_foo(foo) или foo.dispose().

Каждый класс может определять следующие специальные функции-члены :

  • Конструкторы, для приведения объекта в правильное начальное состояние
  • Деструктор, для ответственной очистки
  • Конструктор Копии, для создания нового объекта, который является дубликатом другого
  • Конструктора Перемещения, для создания нового объекта путем передачи данных другого Оператора Назначения Копии
  • , для дублированияобъект в существующий объект
  • Оператор назначения перемещения, для передачи данных между двумя существующими объектами

Это все функции, которые вы можете определить для выполнения любых действий.Но они называются неявно , что означает, что пользователи такого класса не видят эти вызовы функций в своем коде, и они ожидают, что они будут делать предсказуемые вещи.Вы должны убедиться, что ваши классы ведут себя предсказуемо, следуя правилу Three / Five / Zero .

. Есть, конечно, другие инструменты для обмена данными, такие как передача по ссылке, котораяВы уже знаете о.

Множество классов в стандартной библиотеке используют эти специальные функции-члены для реализации специальных поведений, которые очень полезны и помогают пользователям писать безопасный, правильный код.Например:

  • std::vector при копировании всегда будет иметь идентичные элементы, хотя базовый массив и содержащиеся в нем объекты будут разделены.
  • std::unique_ptr оборачивает ресурс, у которого есть только один владелец.Для реализации этого нельзя скопировать .
  • std::shared_ptr оборачивает ресурс, у которого много владельцев.Не совсем понятно, когда нужно очищать такой ресурс, поэтому при копировании shared_ptr выполняется автоматический подсчет ссылок , а ресурс очищается только после того, как с ним покончил последний владелец.
1 голос
/ 22 марта 2019

Это потому, что vector имеет значение семантика: когда вы копируете его, вы получаете истинную копию всех элементов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...