LINUX.ORG.RU

Объясните про allocator_traits::construct в описании std::vector::push_back()

 ,


0

3

Всем привет.

Вчитываюсь в документацию по std::vector. Написано:

http://www.cplusplus.com/reference/vector/vector/push_back/ , секция Exception safety:
If allocator_traits::construct is not supported with val as argument, it causes undefined behavior.

Объясните, плиз:
1. Вкратце, что такое allocator_traits::construct и зачем оно ему нужно.
2. Наглядный пример когда allocator_traits::construct таки не поддерживается, следовательно имеет место undefined behavior.
3. Как проверить, что allocator_traits::construct поддерживается (с помощью static_assert и/или traits).

Спасибо.


Ответ на: комментарий от yoghurt

http://www.cplusplus.com/reference/memory/allocator_traits/construct/

Это я видел. «Конструирует» объект, для которого память уже выделена с помощью алкокатора. Если такого аллокатора нет (интересно, как он это определяет?), то использует new.

Ну и как какой-то тип это может не поддерживать?

Kroz
() автор топика
Ответ на: комментарий от Kroz

Ты можешь присунуть вектору свой собственный аллокатор, который может и не поддерживать конструктор копирования выбранного типа («val as argument»).

yoghurt
()
Ответ на: комментарий от yoghurt

Ты можешь присунуть вектору свой собственный аллокатор, который может и не поддерживать конструктор копирования выбранного типа («val as argument»).

Ага. Так понятней.
Получается, что, если я не использую кастомный аллокатор, то, чтобы не было UB, у типа должен быть конструктор копирования? То есть, с точки зрения валидации, можно проверить с помощью std::is_copy_constructible, все верно?

Kroz
() автор топика
Ответ на: комментарий от Kroz

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

yoghurt
()
Ответ на: комментарий от yoghurt

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

Компилится:

#include <iostream>
#include <type_traits>
#include <vector>

struct A { };
struct B { B(B&&){} };
struct C { C(const C&){} };

int main() {
  std::cout << std::boolalpha;
  std::cout << "is_copy_constructible:" << std::endl;
  std::cout << "int: " << std::is_copy_constructible<int>::value << std::endl;
  std::cout << "A: " << std::is_copy_constructible<A>::value << std::endl;
  std::cout << "B: " << std::is_copy_constructible<B>::value << std::endl;
  std::cout << "C: " << std::is_copy_constructible<C>::value << std::endl;
	
  std::vector<B> v;
	
  return 0;
}

Итого, тип данных вектора обязательно должен иметь конструктор копирования. Тогда немного странно выглядит вот это утверждение:
If a reallocation happens, the strong guarantee is also given if the type of the elements is either copyable or no-throw moveable.

У себя пока навесил static_assert на предмет конструктора копирования. Но если будут еще комментарии, буду рад.

Kroz
() автор топика
Ответ на: комментарий от yoghurt

Ну а push_back где?

Например, вот:

#include <iostream>
#include <type_traits>
#include <vector>

struct A { };
struct B {
        B(){};
        B(B&)=delete;
        B(B&&){};

};
struct C { C(const C&){} };

int main() {
  std::cout << std::boolalpha;
  std::cout << "is_copy_constructible:" << std::endl;
  std::cout << "int: " << std::is_copy_constructible<int>::value << std::endl;
  std::cout << "A: " << std::is_copy_constructible<A>::value << std::endl;
  std::cout << "B: " << std::is_copy_constructible<B>::value << std::endl;
  std::cout << "C: " << std::is_copy_constructible<C>::value << std::endl;

  B varB;
  std::vector<B> v;

  v.push_back(std::move(varB));

  return 0;
}
Или под "with val as argument" мы понимаем конструктор копирования или перемещения? Если моя догадка верна, то сходится. Смущает только, что никакого UB там нет, просто не компилится.

Kroz
() автор топика
Последнее исправление: Kroz (всего исправлений: 3)

http://www.cplusplus.com/

Стоит смотреть в паре с http://en.cppreference.com/

И например, там (http://en.cppreference.com/w/cpp/container/vector/push_back) говорится

void push_back( const T& value ); (1)
void push_back( T&& value ); (2) (since C++11)

-T must meet the requirements of CopyInsertable in order to use overload (1).
-T must meet the requirements of MoveInsertable in order to use overload (2).

Uter
()
Последнее исправление: Uter (всего исправлений: 1)

1. Это placement new в терминах аллокаторов.

2. Видимо, так будет, если написать свою специализацию std::allocator_traits без метода construct(value_type).

3. Стандартный аллокатор всегда все поддерживает. Здесь будет вызываться placement new.

Кстати, конструкторы копирования тут вообще ни при чем (разумеется, value_type должен поддерживать копирование или перемещение, но обсуждаемый параграф не про это).

cord
()
Ответ на: комментарий от cord

конструкторы копирования тут вообще ни при чем

Они тут при том, что std::allocator_traits::construct(value_type) реализует их семантику. Не?

yoghurt
()
Ответ на: комментарий от Uter

Стоит смотреть в паре с http://en.cppreference.com/
И например, там (http://en.cppreference.com/w/cpp/container/vector/push_back) говорится

Это первый топик на этом сайте, который дал нормальное объяснения. Обычно пишут так, что ничего не понятно (я даже понял, как это у них получается).

Если вкратце, то у типа должен быть конструктор копирования либо конструктор перемещения - в зависимости от того, какой вариант push_back() используется. Единственно что, при использовании варианта push_back с rvalue, допустимо не иметь конструктора перемещения, но иметь конструктор копирования c константой (a copy constructor that takes a const T& argument can bind rvalue expressions). То есть мой пример выше не вызовет UB. Вроде так.

Kroz
() автор топика
Ответ на: комментарий от cord

Кстати, конструкторы копирования тут вообще ни при чем

Судя по всему таки причем.

void push_back( const T& value );
// Appends the given element value to the end of the container.
// The new element is initialized as a copy of value.
// T must meet the requirements of CopyInsertable in order to use overload
// CopyInsertable - specifies that an instance of the type can be copy-constructed in-place, in uninitialized storage.
// If A is std::allocator<T>, then this will call placement-new, as by ::new((void*)p) T(rv)

Kroz
() автор топика
Ответ на: комментарий от yoghurt

Семантику - да, но технически они же не обязаны вызываться.

cord
()
Ответ на: комментарий от Kroz

If A is std::allocator<T>

Там в строчке про UB речь про нестандартный аллокатор (потому что со стандартным такого не бывает), а он может не использовать конструктор копирования или перемещения. И их тогда вообще может не быть, и вектор будет работать с таким типом.

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

cord
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.