В настоящее время я прохожу курс C ++ в университете. Я понимаю общую концепцию мелкого и глубокого копирования с использованием векторов, однако в моем учебнике есть пример, который меня смущает.
Пожалуйста, предположим, что это плохо реализованный вектор без определенного конструктора копирования, поэтому он выполняет только поверхностную копию данных.
Я понимаю, что происходит в первой части
В заявлении
vector<int> v2(v1);
вектор
v1
передается как константный аргумент ссылки на векторную копию
конструктор, такv1
не может быть изменено, и переменнаяv2
затем
инициализируется в копию вектораv1
, Каждое поле данных будет
скопированы, и любые изменения, внесенные позже вv2
не должно влиятьv1
, когда
значение вv1.the_data
копируется, обаv1.the_data
а также
v2.the_data
будет указывать на тот же массивТак как
v1.the_data
а такжеv2.the_data
указать на тот же объект,
заявлениеv1[2] = 10;
также меняется
v2[2]
, По этой причине,v2
считается мелкой копией
изv1
,
Однако я изо всех сил пытаюсь понять эту часть. Я не совсем уверен, почему v2.num_items
также не изменится в мелкой копии.
Заявление
v1.push_back(20);
вставит
20
вv1[5]
и изменитсяv1.num_items
до 6, но будет
не изменитьv2.num_items
,
Мои нынешние мысли об этом таковы, что v1.the_data
а также v2.the_data
указывают на одно и то же место в памяти, поэтому они «делят» один и тот же вектор, поэтому, когда к его концу добавляется 20, оба вектора должны получить дополнительное целое число.
Я был бы очень признателен за помощь в понимании того, почему количество предметов не изменится для v2
когда v1
модифицируется.
Предполагая, что мы говорим о стандарте std::vector
:
Когда вы копируете вектор в этом утверждении:
vector<int> v2(v1);
v2 создается путем копирования каждого элемента v1. v1 и v2 не разделяют их память.
Эта часть :
и v1.the_data, и v2.the_data будут указывать на один и тот же массив
Поскольку v1.the_data и v2.the_data указывают на один и тот же объект,
Неправильно.
Вы можете убедить себя, сравнивая базовые адреса массивов каждого из ваших векторов с data()
функция-член.
РЕДАКТИРОВАТЬ :
Предполагая, что вы достаточно сумасшедший, чтобы не использовать std::vector
и использовать реализацию, которая будет «делиться» своим внутренним массивом при копировании (я не буду говорить о проблемах с этим дизайном: кому принадлежит массив? кто delete[]
Это ?)
Вопрос, поднятый вашим учителем, заключается в том, что когда v1
изменяется (например, добавляется элемент), v2
не знает об этом, и имеет неизменный размер.
любой push_back
(или лайки), сделанные для одного вектора, должны соблюдаться каждым другим владельцем массива, чтобы правильно отразить размер массива.
Или :
1) вы реализуете какой-то шаблон наблюдателя, чтобы каждый вектор знал о какой-либо модификации (и это сложнее, чем кажется)
2) вы используете трюки для хранения длины в самом бэкэнд-массиве.
Вы могли бы столкнуться с подобными проблемами, чтобы сделать недействительными все итераторы, когда «разделяемый» массив изменяется с помощью одной из ссылок на векторы … Кошмар! Есть веские причины, по которым контейнеры STL были все разработан для управления собственной памятью, следовательно, всегда обеспечивает глубокую семантику копирования.
Ваш учебник говорит о std::vector
из стандартной библиотеки? Если так, то это неправильно. vector<int> v2(v1);
копировать конструкции v2
от v1
, Это глубокая копия, два контейнера не разделяют хранилище и полностью разделены.
Если вместо этого это плохо реализовано vector
класс и контейнеры разделяют хранилище, затем изменяя существующий элемент в одном будет отражен в другом. Операция как push_back
это изменило один контейнер num_items
но не другие заставили бы их не соглашаться на их размере.
Это утверждение, похоже, предполагает конкретную реализацию
vector
(что не соответствует std::vector
). Предположим,
например, у нас очень наивная реализация:
template <typename T>
class Vector
{
T* myData;
int mySize;
int myCapacity;
public:
void push_back( T const& newValue )
{
if ( mySize == myCapacity ) {
// Extend the capacity...
}
myData[mySize] = newValue;
++ mySize;
}
T& operator[]( int index )
{
return myData[index];
}
};
Если у вас нет конструктора копирования, когда вы копируете вектор,
все три переменные будут одинаковыми: оба вектора будут иметь
указатель на те же данные, того же размера и той же емкости.
Но это копии: когда вы используете []
Вы модифицируете память
указал на myData
, который одинаков в обоих векторах; когда
ты делаешь push_back
на v1
Вы обновляете размер из v1
,
в его локальной копии размера.
Конечно, эта реализация наивна во многих отношениях.
Хорошая реализация чего-то вроде std::vector
требует
изрядное количество мыслей, не только потому, что если требуется глубокая копия
семантика, но и по причинам исключительной безопасности (
конструктор T
может бросить), и чтобы не навязывать
ненужные требования (в частности, конструктор по умолчанию).
Кроме того, если бы я пытался использовать плохо реализованный вектор в качестве примера мелкой копии, я бы не назвал это vector
, так как это сразу вызывает в воображении образ std::vector
, который не должен быть плохо реализован (и не в реализациях библиотеки, которые я знаю).
Проблема в понимании утверждения в этом вопросе заключается в том, следует ли рассматривать вектор как std :: vector или как теоретическую реализацию. std :: vector не допускает поверхностного копирования, и причина указана в утверждении: из-за этого нельзя уважать инварианты.
Теперь возьмем теоретическую реализацию с членами «the_data» и «num_items». Здесь копирование вектора должно дать глубокую копию, но простое копирование «the_data» дает поверхностную копию, потому что копируется только указатель. Это создает несколько проблем: адаптация фактических данных в одном векторе приведет к несовместимому состоянию в другом, и управление памятью больше не может быть выполнено.