Подтвердить что ты не робот

Что возвращает 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

  • но он не всегда является нулевым указателем для вектора нулевого размера

Да, очевидно, я должен был попробовать это, прежде чем спрашивать.

4b9b3361

Ответ 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() быть нулевым указателем?

Да

  1. Можно ли безопасно разыменовать? (Даже если это указывает на хлам.)

Нет

  1. Гарантируется ли разница между двумя разными (нулевыми) векторами?

Нет

Ответ 3

Из стандарта:

23.3.6.4 [vector.data]

T * data() noexcept;

const T * data() const noexcept;

Возвращает: указатель такой, что [data(), data() + size()) является допустимым диапазоном. Для непустой вектор, data() == & front().

Таким образом, это разрешено быть пустым для пустого вектора, но не обязательно разыменовываемым и уникальным.