Реализация контейнера типа std :: vector без неопределенного поведения - PullRequest
0 голосов
/ 25 октября 2018

Это может удивить некоторых кодеров, и, как это ни удивительно, невозможно реализовать std::vector без нестандартной поддержки компиляторов.Проблема, по сути, заключается в способности выполнять арифметику указателей на сырой области хранения.В статье p0593: неявное создание объектов для низкоуровневой манипуляции с объектами , которая появляется в ответе @ShafikYaghmour, четко выявляются проблемы и предлагается модификация стандарта для того, чтобы реализовать реализацию типа контейнера и т. Д.Техника программирования на уровне юриспруденции проще.

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

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

Чтобы получить представление о проблеме, ниже приведено упрощение операций, выполняемых над реализацией std::vector в libc ++ илиlibstdc ++:

void access_value(std::string x);

std::string s1, s2, s3;
//allocation
auto p=static_cast<std::string*>(::operator new(10*sizeof(std::string)));

//push_back s1
new(p) std::string(s1);
access_value(*p);//undefined behavior, p is not a pointer to object

//push_back s2
new(p+1) std::string(s2);//undefined behavior
        //, pointer arithmetic but no array (neither implicit array of size 1)
access_value(*(p+1));//undefined behavior, p+1 is not a pointer to object

//push_back s2
new(p+2) std::string(s3);//undefined behavior
        //, pointer arithmetic but no array
access_value(*(p+2));//undefined behavior, p+2 is not a pointer to object

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

//almost trivialy default constructible
template<class T>
union atdc{
  char _c;
  T value;
  atdc ()noexcept{ }
  ~atdc(){}
};

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

std::string s1, s2, s3;
auto p=::operator new(10*sizeof(std::string));
auto arr = new(p) atdc<std::string>[10];
//pointer arithmetic on arr is allowed

//push_back s1
new(&arr[0].value) std::string(s1); //union member activation
access_value(arr[0].value);

//push_back s2
new(&arr[1].value) std::string(s2);
access_value(arr[1].value);

//push_back s2
new(&arr[2].value) std::string(s2);
access_value(arr[2].value);

Есть ли какое-либо неопределенное поведение в этом коде выше?

1 Ответ

0 голосов
/ 25 октября 2018

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

Включает в себя это обсуждение:

2.3.Динамическое построение массивов

Рассмотрим эту программу, которая пытается реализовать тип, такой как std :: vector (многие детали опущены для краткости):

....

InНа практике этот код работает в диапазоне существующих реализаций, но в соответствии с объектной моделью C ++ в точках #a, #b, #c, #d и #e происходит неопределенное поведение, поскольку они пытаются выполнить арифметику указателя наобласть выделенного хранилища, которая не содержит объект массива.

В местоположениях #b, #c и #d арифметика выполняется на символе *, а в местоположениях #a, #e и #f, арифметика выполняется на T *.В идеале решение этой проблемы должно наполнить оба вычисления определенным поведением.

Approach

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

Подход, использующий объединение adc , имеетпроблема в том, что мы ожидаем, что сможем получить доступ к содержащимся данным через указатель T*, т.е. через std :: vector :: data .Доступ к объединению как T* будет нарушать строгие правила псевдонимов и, следовательно, будет неопределенным поведением.

...