make_unique와 완벽한 전달


216

std::make_unique표준 C ++ 11 라이브러리에 함수 템플릿 이없는 이유는 무엇 입니까? 나는 찾는다

std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));

조금 장황하다. 다음이 훨씬 좋지 않을까요?

auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);

이것은 new멋지게 숨기고 유형을 한 번만 언급합니다.

어쨌든, 여기에 구현하려는 나의 시도는 make_unique다음 과 같습니다.

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

std::forward물건을 컴파일 하는 데 꽤 오랜 시간이 걸렸지 만 그것이 맞는지 확실하지 않습니다. 그렇습니까? 정확히 무엇을 std::forward<Args>(args)...의미합니까? 컴파일러는 무엇을 만드는가?


1
우리가 전에이 논의를 했음에 틀림 없다 ... 또한 unique_ptr두 번째 템플릿 매개 변수를 취해야한다 shared_ptr.
Kerrek SB

5
@Kerrek : 나는 그것을 매개 변수화하는 의미 만들 것이라고 생각하지 않는다 make_unique분명히 일반 구를 통해 할당하기 때문에, 사용자 정의 Deleter가와를 new따라서하고 있어야 사용 평범한 구식 delete:
fredoverflow

1
@Fred : 사실입니다. 제안 된make_uniquenew할당 으로 제한됩니다 ... 글쎄, 작성하고 싶다면 괜찮지 만 왜 그런 것이 표준의 일부가 아닌지 알 수 있습니다.
Kerrek SB

4
사실, make_unique의 생성자 std::unique_ptr가 명시 적이므로 템플릿 을 사용하고 싶습니다. 따라서 unique_ptr함수에서 반환하는 것이 장황 합니다. 또한, 차라리 사용하는 것 auto p = make_unique<foo>(bar, baz)보다std::unique_ptr<foo> p(new foo(bar, baz)) .
Alexandre C.

답변:


157

C ++ 표준화위원회 의장 인 Herb Sutter는 자신의 블로그에 다음 과 같이 썼습니다 .

C ++ 11에 포함되지 않은 make_unique부분은 부분적으로 감독이며 앞으로 확실히 추가 될 것입니다.

또한 OP에서 제공 한 것과 동일한 구현을 제공합니다.

편집 : std::make_unique 이제 C ++ 14의 일부입니다 .


2
make_unique기능 템플릿은 그 자체가 예외 안전한 통화를 보장한다. 발신자가 사용하는 규칙에 의존합니다. 반대로 엄격한 정적 유형 검사 (C ++와 C의 주요 차이점)는 유형을 통해 안전성 을 강화 한다는 아이디어를 기반으로합니다 . 이를 위해 make_unique단순히 함수 대신 클래스가 될 수 있습니다. 예를 들어, 2010 년 5 월의 블로그 기사 를 참조하십시오 . 또한 Herb의 블로그에 대한 토론과 연결되어 있습니다.
건배와 hth. -Alf

1
@ DavidRodríguez-dribeas : 예외 안전 문제를 이해하려면 Herb의 블로그를 읽으십시오. "유도하도록 설계되지 않았다"는 것을 잊어 버리십시오. 그것은 단지 쓰레기입니다. make_unique클래스 만들기에 대한 제안 대신 , 이제는 그것을 생성하는 함수로 만드는 것이 낫다고 생각합니다 make_unique_t. 그 이유는 가장 왜곡 된 구문 분석의 문제입니다 :-).
건배와 hth. -Alf

1
@ Cheersandhth.-Alf : 방금 읽은 기사 make_unique가 강력한 예외 보장 을 제공 한다고 명확하게 설명 하기 때문에 다른 기사를 읽었을 수도 있습니다 . 또는 공구를 용도와 혼합하여 사용하는 경우 예외 안전 기능이 없습니다 . 고려해 void f( int *, int* ){}보면 확실하게 no throw보증을 제공 하지만 추론에 따르면 오용 될 수 있으므로 예외 안전하지 않습니다. 더 나쁜 것은 void f( int, int ) {}예외도 안전하지 않다! : typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...
David Rodríguez-dribeas

2
@ Cheersandhth.-Alf : 위와 같이 구현 된 예외 문제 대해 물었고 make_uniqueSutter의 기사, 내 Google-fu가 나에게 지적한 기사를 알려줍니다.make_unique 위의 진술과 모순되는 강력한 예외 보증 제공하는 . 다른 기사가 있다면 나는 나는 그것을 읽기에 관심. 그래서 내 원래의 질문 make_unique(위에서 정의한 바와 같이) 어떻게 예외적으로 안전하지 않습니까? (Sidenote : 예, 그것이 적용될 수있는 곳에서 예외 안전 을 make_unique 개선 한다고 생각합니다 )
David Rodríguez-dribeas

3
... 또한 make_unique기능을 덜 사용하기 위해 안전하지 않습니다 . 먼저이 유형이 안전하지 않은 것을 보지 못하고 추가 유형을 추가하여 더 안전하게 만드는 방법을 보지 못했습니다 . 내가 아는 것은 이해에 관심이 있다는 것입니다 이 구현이라면 .. 닥터 버크 떠 봐 아무 것도 볼 수 없습니다 미칠 수있는 문제를, 그리고 다른 구현을 해결할 방법에 대해 설명합니다. 무엇입니까 규칙make_unique 에 따라 달라은? 안전을 강화하기 위해 유형 검사를 어떻게 사용 하시겠습니까? 그것들은 내가 대답을 좋아할 두 가지 질문입니다.
David Rodríguez-dribeas

78

훌륭하지만 Stephan T. Lavavej (더 나은 STL)는에 대한 더 나은 솔루션을 제공하며 make_unique, 이는 어레이 버전에서 올바르게 작동합니다.

#include <memory>
#include <type_traits>
#include <utility>

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::false_type, Args&&... args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::true_type, Args&&... args) {
   static_assert(std::extent<T>::value == 0,
       "make_unique<T[N]>() is forbidden, please use make_unique<T[]>().");

   typedef typename std::remove_extent<T>::type U;
   return std::unique_ptr<T>(new U[sizeof...(Args)]{std::forward<Args>(args)...});
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
   return make_unique_helper<T>(std::is_array<T>(), std::forward<Args>(args)...);
}

이것은 그의 핵심 C ++ 6 비디오 에서 볼 수 있습니다 .

make_unique의 STL 버전의 업데이트 된 버전으로 사용할 수 있습니다 N3656 . 이 버전 초안 C ++ 14에 채택 되었습니다.


make_unique는 Stephen T Lavalej가 다음 std 업데이트로 제안했습니다.
tominator

여기에 그것을 추가하는 것에 대한 이야기가 있습니다. channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/…
토미 네이터

왜 모든 불필요한 편집 내용을 xeo로 만들었습니까? 이 코드는 Stephen T. Lavalej가 말한 것과 정확히 같으며 std 라이브러리를 유지 관리하는 dinkumware에서 일합니다. 이미 그 벽에 댓글을 달았으므로 알아야합니다.
tominator

3
에 대한 구현 make_unique은 헤더로 이동해야합니다. 헤더는 네임 스페이스를 가져 와서는 안됩니다 (Sutter / Alexandrescu의 "C ++ Coding Standards"책의 59 번 항목 참조). Xeo의 변경 사항은 나쁜 관행을 권장하지 않습니다.
브렛 쿤스

불행히도, VC2010, 심지어 VC2012에서 지원하지 않습니다. 둘 다 다양한 tempalte 매개 변수를 지원하지 않습니다.
zhaorufei

19

std::make_shared에 대한 속기 만이 아닙니다 std::shared_ptr<Type> ptr(new Type(...));. 그것 없이는 할 수없는 일을합니다.

작업을 수행 std::shared_ptr하려면 실제 포인터의 스토리지를 보유하는 것 외에도 추적 블록을 할당해야합니다. 그러나 std::make_shared실제 객체를 할당 하기 때문에 동일한 메모리 블록에 std::make_shared객체 추적 블록을 모두 할당 할 수 있습니다 .

따라서 std::shared_ptr<Type> ptr = new Type(...);두 개의 메모리 할당 ( 트래킹 블록의 new하나에 대한 하나 std::shared_ptr) std::make_shared<Type>(...)하나 의 메모리 블록을 할당 합니다.

이는의 많은 잠재 사용자에게 중요합니다 std::shared_ptr. a std::make_unique가 할 수 있는 유일한 것은 약간 더 편리합니다. 그 이상은 없습니다.


2
필요하지 않습니다. 힌트는 있지만 필수는 아닙니다.
강아지

3
편의상 뿐만 아니라 경우에 따라 예외 안전성을 향상시킬 수도 있습니다. 이에 대한 Kerrek SB의 답변을 참조하십시오.
Piotr99

19

아무것도 당신이 당신의 자신의 도우미를 작성하는 것을 막을 수는 없지만 make_shared<T>, 라이브러리에서 제공하는 주된 이유 는 실제로 shared_ptr<T>(new T)다른 할당 된 공유 포인터 유형을 생성하기 때문 입니다. 돕는 사람.

귀하 make_unique반면에 래퍼는 주변 단순한 문법 설탕이다 new가, 그것은 아무것도 가져 오지 않는 눈에 기쁘게을 보일 수 있도록하면서, 표현 new테이블에 있습니다. 수정 : 이것은 사실이 아닙니다. new표현식 을 감싸기 위해 함수 호출을 하면 예외 안전을 제공합니다 (예 : 함수를 호출하는 경우) void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&). new서로에 대해 순서가 다른 두 개의 raw를 갖는 것은 하나의 새로운 표현식이 예외로 실패하면 다른 하나는 자원을 누출시킬 수 있음을 의미합니다. make_unique표준에 없는 이유에 관해서는 : 그것은 잊혀졌습니다. (이것은 때때로 발생합니다. 글로벌도 없습니다.std::cbegin 표준에 이 없어도 표준 .)

또한 unique_ptr두 번째 템플릿 매개 변수를 사용하여 어떻게 든 허용해야합니다. 이것은 shared_ptr유형 삭제를 사용하여 저장 하는와 다릅니다. 사용자 정의 기를 유형의 일부로 만들지 않고 하는 것과 다릅니다.


1
@FredOverflow : 공유 포인터는 비교적 복잡한 클래스입니다. 내부적으로 다형성 참조 제어 블록을 유지하지만 여러 종류의 제어 블록이 있습니다. shared_ptr<T>(new T)그중 하나를 make_shared<T>()사용하고 다른 것을 사용합니다. 그것이 좋은 일이며, make-shared 버전은 어떤 의미에서 당신이 얻을 수있는 가장 가벼운 공유 포인터입니다.
Kerrek SB

15
@FredOverflow : shared_ptr동적 메모리 블록을 할당 하여을 만들 때 카운트와 "disposer"작업을 유지합니다 shared_ptr. 포인터를 명시 적으로 전달하면 "새"블록을 작성해야합니다.이 블록을 사용 make_shared하면 오브젝트와 위성 데이터를 단일 메모리 블록 (1 개 )에 묶을 있어 new할당 / 할당이 더 빠르고 조각화가 적으며 (일반적으로) ) 더 나은 캐시 동작.
Matthieu M.

2
나는 다시 시계에 필요가 있다고 생각 shared_ptr의 친구 ...
fredoverflow

4
-1 "반면에, 당신의 make_unique 래퍼는 새로운 표현을 중심으로 한 단순한 구문 설탕이므로 눈에 기분 좋게 보일지 모르지만 테이블에 새로운 것을 가져 오지는 않습니다." 잘못되었습니다. 예외 안전 함수 호출 가능성을 제공합니다. 그러나 보장하지는 않습니다. 이를 위해서는 클래스가 있어야 클래스의 공식적인 인수를 선언 할 수 있습니다 (허브는이를 옵트 인과 옵트 아웃의 차이로 설명했습니다).
건배와 hth. -Alf

1
@ Cheersandhth.-Alf : 네, 맞습니다. 그 이후로 이것을 깨달았습니다. 답을 편집하겠습니다.
Kerrek SB

13

C ++ 11에서는 ..."팩 확장"에도 템플릿 코드로 사용됩니다.

요구 사항은 확장되지 않은 매개 변수 팩을 포함하는 표현식의 접미사로 사용하고 팩의 각 요소에 표현식을 적용하는 것입니다.

예를 들어, 귀하의 예를 바탕으로 :

std::forward<Args>(args)... -> std::forward<int>(1), std::forward<int>(2),
                                                     std::forward<int>(3)

std::forward<Args...>(args...) -> std::forward<int, int, int>(1,2,3)

후자는 틀렸다고 생각합니다.

또한 인수 팩은 확장되지 않은 함수로 전달되지 않을 수 있습니다. 템플릿 매개 변수 팩이 확실하지 않습니다.


1
이 내용이 잘 나온 것을 보게되어 기쁩니다. 가변 템플릿 템플릿도 포함하지 않겠습니까?
Kerrek SB

@ Kerrek : 이상한 구문에 대해 확신이 없기 때문에 많은 사람들이 놀았는지 모르겠습니다. 그래서 나는 내가 아는 것을 계속 유지할 것입니다. 누군가가 동기를 부여하고 지식이 충분하다면 variadic 구문이 완벽하기 때문에 C ++ FAQ 항목을 보증 할 수 있습니다.
Matthieu M.

구문은 std::forward<Args>(args)...로 확장됩니다 forward<T1>(x1), forward<T2>(x2), ....
Kerrek SB

1
: forward실제로는 항상 템플릿 매개 변수가 필요 하다고 생각 합니까?
Kerrek SB

1
@Howard : 그렇습니다! 인스턴스화를 강제하면 경고가 발생합니다 ( ideone.com/GDNHb )
Matthieu M.

5

스테판 T. Lavavej에 의해 구현에 의해 영감을, 나는 배열 범위를 지원되는 make_unique이 좋을 거라 생각 은 GitHub의에이야 그리고 난에 대한 의견을 얻을 싶어요. 이를 통해 다음을 수행 할 수 있습니다.

// create unique_ptr to an array of 100 integers
auto a = make_unique<int[100]>();

// create a unique_ptr to an array of 100 integers and
// set the first three elements to 1,2,3
auto b = make_unique<int[100]>(1,2,3); 
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.