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
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.