Векторы STL с неинициализированным устройством хранения данных?

Оба (a) и (b) приводят к неопределенному поведению. Всегда неопределенное поведение вызывает функцию-член через нулевой указатель. Если функция является статической, она также технически не определена, но есть некоторые споры.


Первое, что нужно понять, - это то, почему это неопределенное поведение для разыменования нулевого указателя. В C ++ 03 здесь есть немного двусмысленности.

Хотя «разыменование нулевого указателя приводит к неопределенному поведению» упоминается в примечаниях как в §1.9 / 4, так и в §8.3.2 / 4, это никогда явно не указано. (Заметки ненормативны.)

Однако можно попытаться вывести его из §3.10 / 2:

lvalue относится к объекту или функции.

blockquote>

При разыменовании результат равен lvalue. Нулевой указатель не ссылается на на объект, поэтому, когда мы используем lvalue, мы имеем неопределенное поведение. Проблема в том, что предыдущее предложение никогда не указано, так что значит «использовать» значение lvalue? Просто даже сгенерируйте его вообще или используйте в более формальном смысле преобразования lvalue-to-rvalue

Независимо от того, что он определенно не может быть преобразован в rvalue (§4.1 / 1):

Если объект, к которому относится lvalue, не является объектом типа T и не является объектом типа, производного от T, или если объект не инициализирован, программа, требующая этого преобразования, неопределенное поведение.

blockquote>

Здесь это определенно неопределенное поведение.

Неоднозначность возникает из того, является ли это неопределенным поведением уважение , но не использует значение из недопустимого указателя (то есть получить значение lvalue, но не преобразовать его в rvalue). Если нет, то int *i = 0; *i; &(*i); четко определен. Это активная проблема .

Итак, у нас есть строгий «разыменовать нулевой указатель, получить неопределенное поведение» и «слабый» использовать нулевой указатель с разделителями, получить неопределенное поведение » Посмотреть.

Теперь рассмотрим вопрос.


Да, (a) приводит к неопределенному поведению. Фактически, если this имеет значение null, то независимо от содержимого функции результат не определен.

Это следует из п. 5.2.5 / 3:

Если E1 имеет тип «указатель на класс X», то выражение E1->E2 преобразуется в эквивалентную форму (*(E1)).E2;

blockquote>

*(E1) будет приводят к неопределенному поведению со строгой интерпретацией, а .E2 преобразует его в rvalue, делая его неопределенным поведением для слабой интерпретации.

Также следует, что это неопределенное поведение непосредственно из (§9.3.1 / 1):

Если нестатическая функция-член класса X вызывается для объекта, который не относится к типу X или типа, полученного из X, поведение не определено.

blockquote>

Со статическими функциями строгая и слабая интерпретация делает разницу. Строго говоря, он не определен:

Статический член может ссылаться на использование синтаксиса доступа к члену класса, и в этом случае оценивается объектное выражение.

blockquote>

То есть он оценивается так же, как если бы он был нестатическим, и мы снова разыменовали нулевой указатель с помощью (*(E1)).E2.

Однако, поскольку E1 не используется в статической членной функции вызов, если мы используем слабую интерпретацию, вызов корректно определен. *(E1) приводит к lvalue, статическая функция разрешена, *(E1) отбрасывается, и функция вызывается. Нет никакого преобразования lvalue-to-rvalue, поэтому нет неопределенного поведения.

В C ++ 0x, начиная с n3126, неопределенность остается. Пока что будьте в безопасности: используйте строгую интерпретацию.

40
задан Jim Hunziker 19 September 2008 в 07:56
поделиться

11 ответов

std::vector должен инициализировать значения в массиве так или иначе, что означает, что некоторого конструктора (или конструктор копии) нужно вызвать. Поведение vector (или любой контейнерный класс) не определено, если необходимо было получить доступ к неинициализированному разделу массива, как будто это было инициализировано.

лучший способ состоит в том, чтобы использовать reserve() и push_back(), так, чтобы конструктор копии использовался, избегая конструкции по умолчанию.

Используя Ваш пример кода:

struct YourData {
    int d1;
    int d2;
    YourData(int v1, int v2) : d1(v1), d2(v2) {}
};

std::vector<YourData> memberVector;

void GetsCalledALot(int* data1, int* data2, int count) {
    int mvSize = memberVector.size();

    // Does not initialize the extra elements
    memberVector.reserve(mvSize + count);

    // Note: consider using std::generate_n or std::copy instead of this loop.
    for (int i = 0; i < count; ++i) {
        // Copy construct using a temporary.
        memberVector.push_back(YourData(data1[i], data2[i]));
    }
}

единственная проблема с вызовом reserve() (или resize()) как это состоит в том, что можно закончить тем, что вызывали конструктора копии чаще, чем Вы должны. Если можно сделать хороший прогноз относительно заключительного размера массива, это лучше к reserve() пространство однажды вначале. Если Вы не знаете заключительного размера, хотя, по крайней мере, количество копий будет минимально в среднем.

В текущей версии C++, внутренний цикл немного неэффективен, поскольку временная ценность создается на стеке, созданном из копии к памяти векторов, и наконец временный файл уничтожается. Однако следующая версия C++ имеет функцию под названием ссылки R-значения (T&&), который поможет.

интерфейс, предоставленный [1 110], не допускает другую опцию, которая должна использовать некоторый подобный фабрике класс для построения значений кроме значения по умолчанию. Вот грубый пример того, на что этот шаблон был бы похож реализованный в C++:

template <typename T>
class my_vector_replacement {

    // ...

    template <typename F>
    my_vector::push_back_using_factory(F factory) {
        // ... check size of array, and resize if needed.

        // Copy construct using placement new,
        new(arrayData+end) T(factory())
        end += sizeof(T);
    }

    char* arrayData;
    size_t end; // Of initialized data in arrayData
};

// One of many possible implementations
struct MyFactory {
    MyFactory(int* p1, int* p2) : d1(p1), d2(p2) {}
    YourData operator()() const {
        return YourData(*d1,*d2);
    }
    int* d1;
    int* d2;
};

void GetsCalledALot(int* data1, int* data2, int count) {
    // ... Still will need the same call to a reserve() type function.

    // Note: consider using std::generate_n or std::copy instead of this loop.
    for (int i = 0; i < count; ++i) {
        // Copy construct using a factory
        memberVector.push_back_using_factory(MyFactory(data1+i, data2+i));
    }
}

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

24
ответ дан Craig M. Brandenburg 5 August 2019 в 05:40
поделиться

Я сделал бы что-то как:

void GetsCalledALot(int* data1, int* data2, int count)
{
  const size_t mvSize = memberVector.size();
  memberVector.reserve(mvSize + count);

  for (int i = 0; i < count; ++i) {
    memberVector.push_back(MyType(data1[i], data2[i]));
  }
}

необходимо определить ctor для типа, который хранится в memberVector, но это - маленькая стоимость, поскольку это даст Вам лучший из обоих миров; никакая ненужная инициализация не сделана, и никакое перераспределение не произойдет во время цикла.

0
ответ дан Andreas Magnusson 5 August 2019 в 05:40
поделиться

Я не думаю, что STL является Вашим ответом. Вы собираетесь нуждаться виду самокрутки решения с помощью перевыделения (). Необходимо будет сохранить указатель и или размер, или число элементов и использование, что для нахождения, где начать добавлять элементы после перевыделения ().

int *memberArray;
int arrayCount;
void GetsCalledALot(int* data1, int* data2, int count) {
    memberArray = realloc(memberArray, sizeof(int) * (arrayCount + count);
    for (int i = 0; i < count; ++i) {
        memberArray[arrayCount + i].d1 = data1[i];
        memberArray[arrayCount + i].d2 = data2[i];
    }
    arrayCount += count;
}
0
ответ дан mbyrne215 5 August 2019 в 05:40
поделиться

От Ваших комментариев до других плакатов похоже, что Вас оставляют с malloc () и друзья. Вектор не позволит Вам не создать элементы.

1
ответ дан fizzer 5 August 2019 в 05:40
поделиться

Сами структуры должны быть в непрерывной памяти, или можно ли сойти с рук наличие вектора структуры*?

Векторы делают копию того, что Вы добавляете к ним, таким образом использование векторов указателей, а не объектов является одним способом улучшить производительность.

0
ответ дан 17 of 26 5 August 2019 в 05:40
поделиться

От Вашего кода похоже, что у Вас есть вектор структур, каждая из которых включает 2 ints. Вы могли вместо этого использовать 2 вектора ints? Тогда

copy(data1, data1 + count, back_inserter(v1));
copy(data2, data2 + count, back_inserter(v2));

Теперь Вы не оплачиваете копирование структуры каждый раз.

1
ответ дан fizzer 5 August 2019 в 05:40
поделиться

Допустить ошибку...

пробуют метод:

std::vector<T>::reserve(x)

Это позволит Вам зарезервировать достаточно памяти для x объектов, не инициализируя никого (Ваш вектор все еще пуст). Таким образом не будет перераспределения до для осмотра x.

, вторая точка - то, что вектор не инициализирует значения для обнуления. Вы тестируете свой код в отладке?

После проверки на g ++, следующий код:

#include <iostream>
#include <vector>

struct MyStruct
{
   int m_iValue00 ;
   int m_iValue01 ;
} ;

int main()
{
   MyStruct aaa, bbb, ccc ;

   std::vector<MyStruct> aMyStruct ;

   aMyStruct.push_back(aaa) ;
   aMyStruct.push_back(bbb) ;
   aMyStruct.push_back(ccc) ;

   aMyStruct.resize(6) ; // [EDIT] double the size

   for(std::vector<MyStruct>::size_type i = 0, iMax = aMyStruct.size(); i < iMax; ++i)
   {
      std::cout << "[" << i << "] : " << aMyStruct[i].m_iValue00 << ", " << aMyStruct[0].m_iValue01 << "\n" ;
   }

   return 0 ;
}

дает следующие результаты:

[0] : 134515780, -16121856
[1] : 134554052, -16121856
[2] : 134544501, -16121856
[3] : 0, -16121856
[4] : 0, -16121856
[5] : 0, -16121856

инициализация, которую Вы видели, была, вероятно, артефактом.

[РЕДАКТИРОВАНИЕ] После комментария изменяют размеры, я изменил код для добавления изменять размеры строки. Изменение размеры эффективно называет конструктора по умолчанию объекта в векторе, но если конструктор по умолчанию ничего не делает, то ничто не инициализируется... Я все еще полагаю, что это был артефакт (мне удалось в первый раз обнулить целый вектор со следующим кодом:

aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;
aMyStruct.push_back(MyStruct()) ;

Так...:-/

[уже РЕДАКТИРУЮТ 2] Как предложенный Arkadiy, решение состоит в том, чтобы использовать встроенного конструктора, берущего желаемые параметры. Что-то как [1 114]

struct MyStruct
{
   MyStruct(int p_d1, int p_d2) : d1(p_d1), d2(p_d2) {}
   int d1, d2 ;
} ;

Это будет, вероятно, встроено в Вашем коде.

, Но необходимо так или иначе изучить код с профилировщиком, чтобы быть уверенными, что эта часть кода является узким местом приложения.

3
ответ дан paercebal 5 August 2019 в 05:40
поделиться

Таким образом, вот проблема, измените размеры, звонит, вставляют, который делает, конструкция копии от значения по умолчанию создала элемент для каждого из недавно добавленных элементов. Для получения этого к 0 стоимости, необходимо записать собственного конструктора по умолчанию И собственного конструктора копии как пустые функции. Выполнение этого Вашему конструктору копии очень плохая идея , потому что это повредит станд.:: внутренние алгоритмы перераспределения вектора.

Сводка: Вы не собираетесь быть способными сделать это со станд.:: вектор.

4
ответ дан Don Neufeld 5 August 2019 в 05:40
поделиться

Используйте станд.:: вектор:: зарезервируйте () метод. Это не изменит размеры вектора, но это выделит место.

1
ответ дан nsanders 5 August 2019 в 05:40
поделиться

Разъяснить на резерве () ответы: необходимо использовать резерв () в сочетании с push_back (). Таким образом, конструктора по умолчанию не называют для каждого элемента, а скорее конструктора копии. Вы все еще подвергаетесь штрафу установки Вашей структуры на стеке и затем копирования его к вектору. С другой стороны, возможно, что, если Вы используете

vect.push_back(MyStruct(fieldValue1, fieldValue2))

, компилятор создаст новый экземпляр непосредственно в памяти thatbelongs к вектору. Это зависит от того, насколько умный оптимизатор. Необходимо проверить сгенерированный код для обнаружения.

8
ответ дан Arkadiy 5 August 2019 в 05:40
поделиться

C++0x добавляет новый шаблон функции-члена emplace_back к vector (который полагается на вариативные шаблоны и совершенную пересылку), который полностью избавляет от временных элементов:

memberVector.emplace_back(data1[i], data2[i]);
9
ответ дан 26 November 2019 в 23:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: