Стандарт c ++ ограничивает арифметику указателей для выполнения в массиве ([Expr.add]) что затрудняет реализацию вектороподобных контейнеров.
Можно реализовать вектороподобный контейнер с реализацией, подобной этой:
//First approach
//Allocation
auto buffer = new unsigned char[2*sizeof(int)];
//Construction
auto p=new(buffer) int{};
new(p+1) int{};
//Example of use of an iterator, assign 10 to the second element.
*(p+1)=10;//UB p+1 is a pointer past the end of an object.
Этот предыдущий код иллюстрирует, как примерно std::vector
реализовано в libstdc ++ и libc ++. Это кажется
что компиляторы принимают этот вид кода как расширение языка c ++.
Если я хочу быть стандартным, я мог бы реализовать vector
и его связанный iterator
таким образом, что операции, выполняемые над вектором и его итератором, могут быть упрощены до следующего кода:
//Second approach
//Allocation:
auto buffer = new unsigned char[2*sizeof(int)];
//Construction
new(buffer) int{};
new(buffer+sizeof(int)) int{};
//Example of use of an iterator assign 10 to the second element
*(std::launder(reinterpret_cast<int*>(buffer+sizeof(int))))=10;
(Первый вопрос, не является ли этот подход также UB? Здесь арифметика указателей выполняется для массива unsigned char, который обеспечивает хранение для объектов int. launder
используется потому что buffer
и int
объекты не являются взаимозаменяемыми указателями)
Проблема с этим вторым подходом — это код, сгенерированный компилятором (GCC):
#include <new>
int test_approach_1(unsigned char* buffer){
//Construction
auto p = new(buffer) int{};
new(p+1) int{10};
//Example of use of an iterator assign 10 to the second element
*(p+1)=13;//UB
return *(p+1);//UB
}
int test_approach_2(unsigned char* buffer){
//Construction
new(buffer) int{};
new(buffer+sizeof(int)) int{10};
//Example of use of an iterator assign 10 to the second element
*(std::launder(reinterpret_cast<int*>(buffer+sizeof(int))))=13;
return *(std::launder(reinterpret_cast<int*>(buffer+sizeof(int))));
}
Сгенерированная сборка:
test_approach_1(unsigned char*):
movabs rax, 55834574848
mov QWORD PTR [rdi], rax
mov eax, 13
ret
test_approach_2(unsigned char*):
movabs rax, 42949672960
mov QWORD PTR [rdi], rax
mov eax, 13
mov DWORD PTR [rdi+4], 13
ret
Код, сгенерированный для test_approach_1
оптимально. Поэтому я думаю, что не буду использовать второй подход (и у меня была бы еще одна причина не использовать его, если кто-то покажет, что это тоже UB.)
Я не нахожу документацию для этих расширений языка, которые позволяют нам реализовывать векторные контейнеры, используя первый подход (это UB согласно стандарту). Есть ли документация для этого? На каком компиляторе можно ожидать его работы и с какими флагами компилятора?
Задача ещё не решена.
Других решений пока нет …