Nice programing

C ++ 17에 std :: construct_at가없는 이유는 무엇입니까?

nicepro 2020. 12. 25. 22:57
반응형

C ++ 17에 std :: construct_at가없는 이유는 무엇입니까?


C ++ 17은를 추가 std::destroy_at하지만 std::construct_at대응하는 것은 없습니다 . 왜 그런 겁니까? 다음과 같이 간단하게 구현할 수 없습니까?

template <typename T, typename... Args>
T* construct_at(void* addr, Args&&... args) {
  return new (addr) T(std::forward<Args>(args)...);
}

완전히 자연스럽지 않은 배치 새로운 구문 을 피할 수 있습니다.

auto ptr = construct_at<int>(buf, 1);  // instead of 'auto ptr = new (buf) int(1);'
std::cout << *ptr;
std::destroy_at(ptr);

std::destroy_at 직접 소멸자 호출에 비해 두 가지 객관적인 개선 사항을 제공합니다.

  1. 중복성을 줄입니다.

    T *ptr = new T;
    //Insert 1000 lines of code here.
    ptr->~T(); //What type was that again?
    

    물론, 우리 모두는 그것을 a로 감싸서 unique_ptr끝내는 것을 선호 하지만, 어떤 이유로 그렇게 할 수 없다면 T중복 요소가 있습니다. 유형을 U으로 변경하면 이제 소멸자 호출을 변경해야합니다. 그렇지 않으면 작업이 중단됩니다. 을 사용 std::destroy_at(ptr)하면 두 곳에서 같은 것을 변경할 필요가 없습니다.

    DRY가 좋습니다.

  2. 다음과 같이 쉽게 할 수 있습니다.

    auto ptr = allocates_an_object(...);
    //Insert code here
    ptr->~???; //What type is that again?
    

    포인터의 유형을 추론하면 삭제가 다소 어려워집니다. 당신은 할 수 없습니다 ptr->~decltype(ptr)(); C ++ 파서는 그런 식으로 작동하지 않기 때문입니다. 뿐만 아니라 decltype유형을 포인터 로 추론 하므로 추론 된 유형에서 포인터 간접 지정을 제거해야합니다. 다음으로 안내 :

    auto ptr = allocates_an_object(...);
    //Insert code here
    using delete_type = std::remove_pointer_t<decltype(ptr)>;
    ptr->~delete_type();
    

    누가 입력하고자하는 것을 ?

반대로 귀하의 가설 std::construct_at배치보다 객관적인 개선을 제공하지 않습니다 new. 두 경우 모두 생성중인 유형을 명시해야합니다. 생성자에 대한 매개 변수는 두 경우 모두 제공되어야합니다. 두 경우 모두 메모리에 대한 포인터를 제공해야합니다.

따라서 당신의 가설에 의해 해결 될 필요가 없습니다 std::construct_at.

그리고 새로운 배치보다 객관적으로 능력떨어 집니다. 다음과 같이 할 수 있습니다.

auto ptr1 = new(mem1) T;
auto ptr2 = new(mem2) T{};

이것들은 다릅니다 . 첫 번째 경우 개체는 기본적으로 초기화되어 초기화되지 않은 상태로 남을 수 있습니다. 두 번째 경우에는 개체가 값으로 초기화됩니다.

당신의 가설 std::construct_at 당신이 원하는 것을 고를 수 없습니다 . 매개 변수를 제공하지 않으면 기본 초기화를 수행하는 코드가있을 수 있지만 값 초기화를위한 버전을 제공 할 수 없습니다. 매개 변수없이 값을 초기화 할 수 있지만 기본적으로 개체를 초기화 할 수는 없습니다.


그런 일이 있지만 예상대로 이름이 지정되지 않았습니다 .

  • uninitialized_copy 는 객체 범위를 초기화되지 않은 메모리 영역으로 복사합니다.

  • uninitialized_copy_n (C ++ 11)은 여러 객체를 초기화되지 않은 메모리 영역 (함수 템플릿)에 복사합니다.

  • uninitialized_fill 은 범위 (함수 템플릿)에 의해 정의 된 초기화되지 않은 메모리 영역에 객체를 복사합니다.

  • uninitialized_fill_n 은 객체를 시작과 카운트 (함수 템플릿)로 정의 된 초기화되지 않은 메모리 영역에 복사합니다.
  • uninitialized_move (C ++ 17)는 객체 범위를 초기화되지 않은 메모리 영역 (함수 템플릿)으로 이동합니다.
  • uninitialized_move_n (C ++ 17)은 많은 객체를 초기화되지 않은 메모리 영역 (함수 템플릿)으로 이동합니다.
  • uninitialized_default_construct (C ++ 17)는 범위 (함수 템플릿)로 정의 된 초기화되지 않은 메모리 영역에서 기본 초기화에 의해 객체를 생성합니다.
  • uninitialized_default_construct_n (C ++ 17)은 시작 및 개수 (함수 템플릿)로 정의 된 초기화되지 않은 메모리 영역에서 기본 초기화로 개체를 생성합니다.
  • uninitialized_value_construct (C ++ 17)는 범위 (함수 템플릿)로 정의 된 초기화되지 않은 메모리 영역에서 값 초기화에 의해 개체를 생성합니다.
  • uninitialized_value_construct_n (C ++ 17)은 시작 및 개수로 정의 된 초기화되지 않은 메모리 영역에서 값 초기화를 통해 개체를 생성합니다.

있습니다 std::allocator_traits::construct. 이전에는에서 하나 더 std::allocator있었지만 제거 된 이유는 표준위원회 문서 D0174R0에 있습니다.


std::construct_atC ++ 20에 추가되었습니다. 그렇게 한 논문은 More constexpr container 입니다. 아마도 이것은 C ++ 17의 새로운 배치에 비해 충분한 이점이없는 것으로 보였지만 C ++ 20은 상황을 바꿉니다.

이 기능을 추가 한 제안의 목적은를 포함한 constexpr 메모리 할당을 지원하는 것 std::vector입니다. 이를 위해서는 할당 된 저장소에 개체를 구성하는 기능이 필요합니다. 그러나의 관점에서 단순한 배치 새로운 거래가 void *아닙니다 T *. constexpr평가는 현재 원시 저장소에 액세스 할 수있는 기능이 없으며위원회는이를 그대로 유지하려고합니다. 라이브러리 함수 std::construct_at는 형식화 된 인터페이스를 추가합니다 constexpr T * construct_at(T *, Args && ...).

또한 사용자가 생성되는 형식을 지정할 필요가 없다는 장점이 있습니다. 포인터의 유형에서 추론됩니다. 새로운 배치를 올바르게 호출하는 구문은 다소 끔찍하고 직관적이지 않습니다. std::construct_at(ptr, args...)비교하십시오 ::new(static_cast<void *>(ptr)) std::decay_t<decltype(*ptr)>(args...).


construct구문상의 설탕을 제공하지 않는 것 같습니다. 또한 새로운 배치보다 효율성이 떨어집니다. 참조 인수에 바인딩하면 임시 구체화 및 추가 이동 / 복사 구성이 발생합니다.

struct heavy{
   unsigned char[4096];
   heavy(const heavy&);
};
heavy make_heavy(); // Return a pr-value
auto loc = ::operator new(sizeof(heavy));
// Equivalently: unsigned char loc[sizeof(heavy)];

auto p = construct<heavy>(loc,make_heavy()); // The pr-value returned by
         // make_heavy is bound to the second argument,
         // and then this arugment is copied in the body of construct.

auto p2 = new(loc) auto(make_heavy()); // Heavy is directly constructed at loc
       //... and this is simpler to write!

불행히도 함수를 호출 할 때 이러한 추가 복사 / 이동 구성을 피할 수있는 방법이 없습니다. 전달은 거의 완벽합니다.

한편, construct_at도서관에서는 표준 도서관 어휘를 완성 할 수 있습니다.


I think there should be a standard construct-function. In fact libc++ has one as an implementation detail in the file stl_construct.h.

namespace std{
...
  template<typename _T1, typename... _Args>
    inline void
    _Construct(_T1* __p, _Args&&... __args)
    { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
...
}

I think is it something useful to have because it allows to make "placement new" a friend. This is a great customization point for a move-only type that need uninitialized_copy into the default heap (from an std::initializer_list element for example.)


I have my own container library that reimplements a detail::uninitialized_copy (of a range) to use a custom detail::construct:

namespace detail{
    template<typename T, typename... As>
    inline void construct(T* p, As&&... as){
        ::new(static_cast<void*>(p)) T(std::forward<As>(as)...);
    }
}

Which is declared a friend of a move-only class to allow copy only in the context of placement new.

template<class T>
class my_move_only_class{
    my_move_only_class(my_move_only_class const&) = default;
    friend template<class TT, class...As> friend void detail::construct(TT*, As&&...);
public:
    my_move_only_class(my_move_only_class&&) = default;
    ...
};

ReferenceURL : https://stackoverflow.com/questions/52966560/why-isnt-there-a-stdconstruct-at-in-c17

반응형