std :: optional을 어떻게 사용해야합니까?


133

나는의 문서를 읽고 있어요 std::experimental::optional그리고 나는 그것이 무엇에 대한 좋은 생각을 가지고,하지만 난 이해가 안 돼요 나는 그것을 사용한다 또는 내가 그것을 사용하는 방법. 이 사이트에는 아직 예제가 포함되어 있지 않으므로이 객체의 진정한 개념을 이해하기가 더 어렵습니다. 언제 std::optional사용하는 것이 좋으며, 이전 표준 (C ++ 11)에서 발견되지 않은 것을 어떻게 보상합니까?


19
boost.optional 문서가 되거 수 있습니다.
juanchopanza

std :: unique_ptr과 같은 것으로 일반적으로 동일한 사용 사례를 제공 할 수 있습니다. 나는 당신이 새로운 것에 반대하는 것을 가지고 있다면, 선택은 바람직 할 것 같지만, 새로운 것에 대한 그런 종류의 견해를 가진 (개발자 | 응용 프로그램)는 소수에 불과합니다 ... AFAICT, 선택은 좋은 생각이 아닙니다. 최소한 우리 대부분은 편안하게 살 수 있습니다. 개인적으로, unique_ptr과 선택적 중 하나를 선택할 필요가없는 세상에서 더 편안 할 것입니다. 나를 미치게하지만, Zen of Python은 옳다 : 무언가를 할 수있는 올바른 방법이있다!
allyourcode

19
우리는 항상 삭제해야 할 힙에 무언가를 할당하고 싶지는 않으므로 unique_ptr이 옵션을 대체하지 않습니다.
Krum

5
@allyourcode 대체 포인터가 없습니다 optional. optional<int>또는 원하는 것을 상상해보십시오 <char>. 동적으로 할당하고 역 참조한 다음 삭제해야한다고 생각하는 것이 실제로 "Zen"이라고 생각합니까?
underscore_d

1
또 다른 차이점은, 포함 된 값의 힙 할당 VS 스택에서 의심 생성 템플릿 인수의 경우 불완전 형 수 없다는 것이다 std::optional(IT가 될 수 있지만 std::unique_ptr). 보다 정확하게는이 표준은 T [...]가 Destructible의 요구 사항을 충족 해야한다고 요구 합니다.
dan_din_pantelimon

답변:


172

내가 생각할 수있는 가장 간단한 예는 다음과 같습니다.

std::optional<int> try_parse_int(std::string s)
{
    //try to parse an int from the given string,
    //and return "nothing" if you fail
}

대신 다음 서명에서와 같이 참조 인수를 사용하여 동일한 작업을 수행 할 수 있지만 사용 std::optional하면 서명과 사용법이 더 좋아집니다.

bool try_parse_int(std::string s, int& i);

이것을 할 수있는 또 다른 방법은 특히 나쁘다 :

int* try_parse_int(std::string s); //return nullptr if fail

이를 위해서는 동적 메모리 할당이 필요하고 소유권 등이 걱정됩니다. 위의 다른 두 서명 중 하나를 항상 선호하십시오.


다른 예시:

class Contact
{
    std::optional<std::string> home_phone;
    std::optional<std::string> work_phone;
    std::optional<std::string> mobile_phone;
};

대신 std::unique_ptr<std::string>각 전화 번호에 대해 비슷한 것을 사용하는 것이 좋습니다 . std::optional성능에 큰 데이터 지역성을 제공합니다.


다른 예시:

template<typename Key, typename Value>
class Lookup
{
    std::optional<Value> get(Key key);
};

조회에 특정 키가 없으면 "값 없음"을 반환 할 수 있습니다.

나는 이것을 다음과 같이 사용할 수 있습니다 :

Lookup<std::string, std::string> location_lookup;
std::string location = location_lookup.get("waldo").value_or("unknown");

다른 예시:

std::vector<std::pair<std::string, double>> search(
    std::string query,
    std::optional<int> max_count,
    std::optional<double> min_match_score);

이는 가능한 모든 조합 max_count(또는 그렇지 않은) 을 취하는 4 개의 기능 과부하를 갖는 것보다 훨씬 더 의미가 있습니다 min_match_score!

또한 제거 저주 "패스를 -1위해 max_count또는"패스는 제한하지 않으려면 " std::numeric_limits<double>::min()에 대한 min_match_score당신이 최소한의 점수를 원하지 않는 경우"를!


다른 예시:

std::optional<int> find_in_string(std::string s, std::string query);

쿼리 문자열이에 없으면 s"no int"를 원합니다. 누군가가이 목적으로 사용하기로 결정한 특별한 값이 아닙니다 (-1?).


추가 예제는 boost::optional 설명서를 참조하십시오 . boost::optional그리고 std::optional기본적으로 행동과 사용의 측면에서 동일합니다.


13
@gnzlbg std::optional<T>는 a T와 a bool입니다. 멤버 함수 구현은 매우 간단합니다. 성능을 사용할 때 실제로는 걱정할 필요가 없습니다. 무언가 선택적인 경우가 있습니다.이 경우 종종 작업에 적합한 도구입니다.
Timothy Shields

8
@TimothyShields std::optional<T>는 그보다 훨씬 더 복잡합니다. 그것은 new다른 것들 constexpr중에서 리터럴 유형 (즉,와 함께 사용 ) 을 만들기 위해 적절한 정렬과 크기로 배치 와 훨씬 더 많은 것들을 사용 합니다. 순진 Tbool접근 방식은 꽤 빨리 실패합니다.
Rapptz

15
@Rapptz 라인 256 : union storage_t { unsigned char dummy_; T value_; ... }라인 289 : struct optional_base { bool init_; storage_t<T> storage_; ... }어떻게 즉 없습니다 "는 Tbool"? 나는 구현이 매우 까다 롭고 사소하지 않다는 것에 완전히 동의하지만, 개념적으로 그리고 구체적으로 유형은 a T와 a bool입니다. "순진 Tbool접근 방식은 꽤 빨리 실패합니다." 코드를 볼 때 어떻게이 진술을 할 수 있습니까?
Timothy Shields

12
@Rapptz는 여전히 bool과 int를위한 공간을 저장하고 있습니다. 유니온은 T가 실제로 원하지 않는 경우 옵션을 구성하지 않도록하기 위해 있습니다. 여전히 모든 경우에 T를 구성하는 struct{bool,maybe_t<T>}노조가 있습니다 struct{bool,T}.
PeterT

12
@allyourcode 아주 좋은 질문입니다. 모두 std::unique_ptr<T>std::optional<T>의 역할 수행 어떤 의미에서하는 "옵션을 T." 나는 결코 것 등 추가 할당, 메모리 관리, 데이터 지역, 이동의 비용 : 나는 "구현 세부 사항"으로 그들 사이의 차이를 설명하는 것이 std::unique_ptr<int> try_parse_int(std::string s);이 아무런 이유가없는 경우에도 모든 통화에 대해 할당 발생할 수있어, 예를 들어 . 나는 클래스가 없을 것입니다 std::unique_ptr<double> limit;-왜 할당을하고 데이터 지역성을 잃습니까?
Timothy Shields

35

예를 들면, 새로 채택 된 논문 : N3672, std :: optional :

 optional<int> str2int(string);    // converts int to string if possible

int get_int_from_user()
{
     string s;

     for (;;) {
         cin >> s;
         optional<int> o = str2int(s); // 'o' may or may not contain an int
         if (o) {                      // does optional contain a value?
            return *o;                  // use the value
         }
     }
}

13
"오차"의미를 갖기 위해 "가상"값을 "가정"값으로 전달하는 대신, 업 / 다운 콜 계층 구조 에 대한 정보를 전달할 수 있기 때문 int입니다.
Luis Machuca

1
@ 위즈 이것은 실제로 좋은 예입니다. (A) str2int()원하는 변환을 구현할 수 있습니다 . (B)를 얻는 방법에 관계없이 string s, (C) optional<int>어리석은 마법 번호, bool/ reference 또는 동적 할당 기반 방식 대신에 완전한 의미를 전달합니다. 하고 있어요
underscore_d

10

하지만 언제 사용해야하는지 또는 어떻게 사용해야하는지 이해할 수 없습니다.

API를 작성할 때 "반환이없는"값이 오류가 아님을 표현하려는 경우를 고려하십시오. 예를 들어, 소켓에서 데이터를 읽고 데이터 블록이 완료되면 구문 분석하여 리턴합니다.

class YourBlock { /* block header, format, whatever else */ };

std::optional<YourBlock> cache_and_get_block(
    some_socket_object& socket);

추가 된 데이터가 구문 분석 가능한 블록을 완성한 경우 처리 할 수 ​​있습니다. 그렇지 않으면 데이터를 읽고 추가하십시오.

void your_client_code(some_socket_object& socket)
{
    char raw_data[1024]; // max 1024 bytes of raw data (for example)
    while(socket.read(raw_data, 1024))
    {
        if(auto block = cache_and_get_block(raw_data))
        {
            // process *block here
            // then return or break
        }
        // else [ no error; just keep reading and appending ]
    }
}

편집 : 나머지 질문과 관련하여 :

std :: optional이 좋은 선택 일 때

  • 값을 계산하고 반환해야 할 경우 출력 값 (생성되지 않을 수 있음)에 대한 참조를 얻는 것보다 의미가 값으로 반환되는 것이 좋습니다.

  • 클라이언트 코드 출력 값을 확인 해야하는지 확인하려는 경우 (클라이언트 코드를 작성하는 사람은 오류를 확인하지 않을 수 있습니다-초기화되지 않은 포인터를 사용하려고하면 코어 덤프가 표시됩니다. 초기화 된 std :: optional, catchable 예외가 발생합니다).

[...] 그리고 이전 표준 (C ++ 11)에서 발견되지 않은 것을 어떻게 보상합니까?

C ++ 11 이전에는 "값을 반환 할 수없는 함수"에 대해 다른 인터페이스를 사용해야했습니다. 포인터로 반환하고 NULL을 확인하거나 출력 매개 변수를 승인하고 "사용할 수 없음"에 대한 오류 / 결과 코드를 반환했습니다. ".

둘 다 클라이언트 구현자가 올바른 노력을 기울 이도록 추가적인 노력과주의를 기울이며 둘 다 혼란의 원천입니다 (첫 번째는 클라이언트 구현자가 작업을 할당으로 생각하도록하고 포인터 처리 논리를 구현하기 위해 클라이언트 코드를 요구하고 두 번째는 허용하는 것입니다) 유효하지 않은 / 초기화되지 않은 값을 사용하여 벗어날 수있는 클라이언트 코드).

std::optional 이전 솔루션으로 발생하는 문제를 훌륭하게 처리합니다.


나는 그들이 기본적으로 동일하다는 것을 알고 있지만 왜 boost::대신 대신 사용하고 std::있습니까?
0x499602D2

4
고마워-나는 그것을 고쳤습니다 ( boost::optional약 2 년 동안 사용한 후에 내 전두엽 피질에 하드 코딩 되었기 때문에 사용했습니다).
utnapistim

1
여러 블록이 완료되면 하나만 반환하고 나머지는 버릴 수 있기 때문에 이것은 나쁜 예입니다. 이 함수는 대신 비어있을 수있는 블록 모음을 반환해야합니다.
벤 Voigt

네가 옳아; 나쁜 예였다. "사용 가능한 경우 첫 번째 블록 구문 분석"에 대한 예제를 수정했습니다.
utnapistim

4

필자는 종종 옵션 파일을 사용하여 구성 파일에서 가져온 옵션 데이터, 즉 XML 문서의 예상 요소 (예 : 아직 필요하지 않은 요소가있는 요소)가 선택적으로 제공되는 경우를 명시 적으로 표시 할 수 있습니다. 데이터는 실제로 XML 문서에있었습니다. 특히 데이터가 "설정되지 않은"상태, "빈"및 "설정된"상태 (퍼지 논리)를 가질 수있는 경우. 선택 사항 인 set 및 not set을 사용하면 값이 0 또는 null 인 경우 비어 있습니다.

"not set"값이 "empty"와 어떻게 다른지 보여줍니다. 개념적으로, int (int * p)에 대한 포인터는 이것을 나타낼 수 있습니다. 여기서 null (p == 0)이 설정되지 않고 0 값 (* p == 0)이 설정되고 비어 있습니다. (* p <> 0)이 값으로 설정되었습니다.

실제 예를 들어, 렌더링 플래그라는 값을 가진 XML 문서에서 가져온 지오메트리 조각이 있는데, 지오메트리가 렌더링 플래그를 무시하거나 (세트) 렌더링 플래그를 비활성화하거나 (0으로 설정) 단순히 렌더 플래그 (설정되지 않음)에 영향을주는 경우 선택 사항은이를 나타내는 명확한 방법입니다.

분명히이 예제에서 int에 대한 포인터는 더 나은 구현을 제공 할 수있는 목표 또는 더 나은 공유 포인터를 달성 할 수 있지만이 경우 코드 명확성에 관한 것입니다. 널은 항상 "설정되지 않음"입니까? 포인터로 널 (null)이 그대로 할당되거나 생성되지 수단으로는하지만, 그것은, 분명하지 않다 , 아직 수있는 것은 아니다 평균 "으로 설정하지". 포인터를 해제해야하고 실제로는 0으로 설정해야하지만 공유 포인터와 같이 옵션을 사용하면 명시 적으로 정리 할 필요가 없으므로 정리와 믹싱을 걱정할 필요가 없습니다. 옵션이 설정되지 않았습니다.

코드 명확성에 관한 것입니다. 명확성은 코드 유지 관리 및 개발 비용을 줄입니다. 코드 의도에 대한 명확한 이해는 엄청나게 가치가 있습니다.

이를 나타 내기 위해 포인터를 사용하려면 포인터 개념을 오버로드해야합니다. "널 (null)"을 "설정되지 않음"으로 표시하기 위해 일반적으로이 의도를 설명하는 코드를 통해 하나 이상의 주석을 볼 수 있습니다. 선택 사항 대신 나쁜 해결책은 아니지만 주석은 강제 할 수 없으므로 (예 : 컴파일) 주석이 아닌 암시 적 구현을 ​​항상 선택합니다. 개발을위한 이러한 암시 적 항목의 예 (순전히 의도를 제공하기 위해 개발중인 기사)에는 다양한 C ++ 스타일 캐스트, "const"(특히 멤버 함수에 대한) 및 "bool"유형이 포함됩니다. 모든 사람이 의도 나 의견을 따르는 한 이러한 코드 기능이 실제로 필요한 것은 아닙니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.