Что возвращает vec.data(), если vec.size() == 0?
cppreference имеет эту заметку для std::vector::data
:
Возвращает указатель на базовый массив, служащий в качестве хранилища элементов. Указатель таков, что диапазон [data(); data() + size())
всегда является допустимым диапазоном, даже если контейнер пуст.
Что здесь означает "действительный диапазон"? Что вернет data()
, если вектор имеет нулевую длину?
В частности, для вектора нулевой длины:
- Может ли
data()
быть нулевым указателем?
- Можно ли безопасно разыменовать? (Даже если это указывает на хлам.)
- Гарантируется ли разница между двумя разными (нулевыми) векторами?
Я работаю с библиотекой C, которая берет массивы и не допускает нулевой указатель даже для массива нулевой длины. Однако на самом деле он не разыскивает указатель хранения массива, если длина массива равна нулю, он просто проверяет, является ли это NULL
. Я хочу убедиться, что я могу безопасно передать data()
в эту библиотеку C, поэтому единственным актуальным вопросом является (1) выше. (2) и (3) просто из любопытства в случае возникновения подобной ситуации.
Обновление
На основе комментариев, которые не были переданы в ответы, мы можем попробовать следующую программу:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
cout << v.data() << endl;
v.push_back(1);
cout << v.data() << endl;
v.pop_back();
cout << v.data() << endl;
v.shrink_to_fit();
cout << v.data() << endl;
return 0;
}
С моим компилятором он выводит:
0x0
0x7f896b403300
0x7f896b403300
0x0
Это показывает, что:
-
data()
действительно может быть нулевым указателем, поэтому ответы: (1) yes (2) no (3) no
-
но он не всегда является нулевым указателем для вектора нулевого размера
Да, очевидно, я должен был попробовать это, прежде чем спрашивать.
Ответы
Ответ 1
Слишком долго для комментария, размещенного здесь.
Я ожидал, что итераторы будут nullptr для пустой последовательности, поэтому я ее протестировал.
#include <iostream>
#include <vector>
void pr(std::vector<int>& v){
std::cout << &*v.begin() << ", " << &*v.end() << "\n";
}
// technically UB, but for this experiment I don't feel too bad about it.
// Thanks @Revolver
int main(int argc, char** argv) {
std::vector<int> v1;
std::vector<int> v2;
pr(v1);
pr(v2);
return 0;
}
И это действительно печатает
0, 0
0, 0
Теперь для пустого контейнера единственной разумной операцией для допустимого диапазона является begin() == end()
. И нет, мусор не может быть разыменован, поэтому * v.begin() не вызывает беспокойства.
Ответ 2
"допустимый диапазон" определяется [iterator.requirements.general]/7 (С++ 14):
"Диапазон [i,j)
действителен тогда и только тогда, когда j
доступен из i
".
К счастью, С++ определяет, что добавление 0
в нулевой указатель дает нулевой указатель. Итак, нулевой указатель доступен из нулевого указателя? Это определяется точкой 6 того же раздела:
Итератор j
называется достижимым из итератора i
тогда и только тогда, когда существует конечная последовательность приложений выражения ++i
, которая делает i == j
.
Последовательность нулевой длины является конечной последовательностью, поэтому data()
может возвращать нулевой указатель.
Соответственно, ответы на ваши вопросы:
- Может ли
data()
быть нулевым указателем?
Да
- Можно ли безопасно разыменовать? (Даже если это указывает на хлам.)
Нет
- Гарантируется ли разница между двумя разными (нулевыми) векторами?
Нет
Ответ 3
Из стандарта:
23.3.6.4 [vector.data]
T * data() noexcept;
const T * data() const noexcept;
Возвращает: указатель такой, что [data(), data() + size()) является допустимым диапазоном. Для непустой вектор, data() == & front().
Таким образом, это разрешено быть пустым для пустого вектора, но не обязательно разыменовываемым и уникальным.