새로운 코드에서 C ++ 17의 [[nodiscard]]를 거의 모든 곳에서 사용하지 않는 이유는 무엇입니까?


70

C ++ 17은 [[nodiscard]]속성을 도입하여 프로그래머가 반환 된 객체를 호출자가 버릴 경우 컴파일러가 경고를 생성하는 방식으로 함수를 표시 할 수 있습니다. 전체 클래스 유형에 동일한 속성을 추가 할 수 있습니다.

원래 제안 에서이 기능의 동기에 대해 읽었 으며 C ++ 20은 표준 값에 속성을 추가한다는 것을 알고 있습니다 std::vector::empty.

시원하고 유용한 기능입니다. 실제로, 그것은 거의 너무 유용한 것 같습니다 . 내가 읽는 모든 곳 [[nodiscard]]에서 사람들은 선택한 기능이나 유형에 추가하고 나머지는 잊어 버린 것처럼 토론합니다. 그러나 왜 새 코드를 작성할 때 무시할 수없는 값이 특별한 경우 여야합니까? 폐기 된 반환 값은 일반적으로 버그 또는 최소한의 리소스 낭비가 아닙니까?

그리고 컴파일러가 가능한 한 많은 오류를 잡아야하는 C ++ 자체의 설계 원칙 중 하나가 아닙니까?

그렇다면 [[nodiscard]]거의 모든 단일 비 void기능 및 거의 모든 단일 클래스 유형에 고유의 비 유효 코드를 추가하지 않겠습니까?

내 코드 에서이 작업을 시도했지만 너무 장황하여 Java처럼 느껴지기 시작한다는 것을 제외하고는 정상적으로 작동합니다. 의도를 표시하는 다른 몇 가지 경우를 제외하고 기본적으로 버려진 반환 값에 대해 컴파일러 에게 경고 하는 것이 훨씬 더 자연스럽게 보일 것입니다 [*] .

표준 제안서, 블로그 항목, 스택 오버플로 질문 또는 인터넷의 다른 곳에서이 가능성에 대한 토론이 전혀 없었으므로 뭔가 빠진 것이 틀림 없습니다.

새로운 C ++ 코드에서는 왜 그런 역학이 이해가되지 않습니까? 자세한 정보가 [[nodiscard]]거의 모든 곳 에서 사용하지 않는 유일한 이유 입니까?


[*] 이론적으로, 표준 라이브러리 구현 [[maydiscard]]과 같은 기능에 소급하여 추가 할 수 있는 속성이 대신있을 수 있습니다 printf.


22
반환 값 현명하게 버릴 있는 많은 함수가 있습니다 . operator =예를 들어. 그리고 std::map::insert.
user253751

2
@immibis : 맞습니다. 표준 라이브러리는 이러한 기능으로 가득합니다. 그러나 내 자신의 코드에서는 극히 드문 경향이 있습니다. 라이브러리 코드 대 응용 프로그램 코드의 문제라고 생각하십니까?
Christian Hackl

9
"...
아마도

3
@ChristianHackl이 의견은 "language bashing"에 대한 올바른 장소가 아닐 것입니다. 물론 Java도 다른 언어에 비해 자세합니다. 그러나 헤더 파일, 할당 연산자, 복사 생성자 및 적절한 사용법은 C ++에서 상당히const 단순한 "클래스"클래스 (또는 "일반적인 오래된 데이터 오브젝트")를 부 풀릴 수 있습니다 .
Marco13

2
@ Marco13 : 한 의견에서 너무 많은 요점을 해결할 수 없습니다. 간단히 말해 : Java에는 헤더 파일이 없으므로 파일 수가 줄어들지 만 커플 링이 증가합니다. OTOH는 모든 최상위 클래스를 자체 파일에 넣고 모든 함수를 클래스에 넣도록 강제합니다. C ++에서는 할당 연산자를 직접 구현하거나 생성자를 직접 복사하지 않습니다. 이러한 함수는 std::vectoror 와 같은 표준 라이브러리 클래스와 관련 std::unique_ptr이 있으며 클래스 정의에 데이터 멤버가 필요합니다. 나는 두 언어로 일했습니다. Java는 괜찮은 언어이지만 더 장황합니다.
Christian Hackl

답변:


73

이전 표준과 호환 될 필요가없는 새 코드에서는 가능한 경우 해당 속성을 사용하십시오. 그러나 C ++의 경우 [[nodiscard]]기본값을 잘못 설정합니다. 당신은 제안합니다 :

의도를 표시하는 몇 가지 다른 경우를 제외하고 기본적으로 컴파일러가 버려진 반환 값에 대해 경고하게하는 것이 훨씬 자연스럽게 보입니다.

이로 인해 기존의 올바른 코드로 인해 많은 경고가 발생합니다. 기존 코드가 여전히 성공적으로 컴파일되기 때문에 이러한 변경은 기술적으로 이전 버전과 호환되는 것으로 간주 될 수 있지만 실제로 의미론이 크게 변경 될 수 있습니다.

코드 기반이 매우 큰 기존의 성숙한 언어에 대한 디자인 결정은 완전히 새로운 언어에 대한 디자인 결정과 반드시 ​​다릅니다. 이것이 새로운 언어라면, 기본적으로 경고는 의미가 있습니다. 예를 들어, Nim 언어는 불필요한 값을 명시 적으로 버려야합니다. 이는 C ++의 모든 표현식 명령문을 cast로 랩핑하는 것과 유사합니다 (void)(...).

[[nodiscard]]속성 두 가지 경우에 가장 유용합니다 :

  • 함수가 특정 결과를 반환하는 것 이상의 효과가없는 경우, 즉 순수합니다. 결과가 사용되지 않으면 호출은 확실히 쓸모가 없습니다. 반면에 결과를 폐기하는 것은 올바르지 않습니다.

  • 반환 값을 확인해야하는 경우 (예 : 던지기 대신 오류 코드를 반환하는 C와 유사한 인터페이스) 이것이 주요 사용 사례입니다. 관용적 C ++의 경우에는 매우 드 rare니다.

이 두 경우는 값을 반환하는 불순한 함수의 공간을 남겨두고 경고가 잘못 될 수 있습니다. 예를 들면 다음과 같습니다.

  • .pop()요소를 제거하고 제거 된 요소의 사본을 리턴하는 메소드가 있는 큐 데이터 유형을 고려하십시오 . 이러한 방법은 종종 편리합니다. 그러나 복사본을 얻지 않고 요소 만 제거하려는 경우가 있습니다. 그것은 완벽하게 합법적이며 경고는 도움이되지 않습니다. 다른 디자인 (예 std::vector:)은 이러한 책임을 분할하지만 다른 단점이 있습니다.

    어떤 경우에는 어쨌든 사본을 작성해야하므로 RVO 덕분에 사본을 반환하면 무료입니다.

  • 추가 작업을 수행 할 수 있도록 각 작업이 개체를 반환하는 유연한 인터페이스를 고려하십시오. C ++에서 가장 일반적인 예는 스트림 삽입 연산자 <<입니다. [[nodiscard]]각각의 모든 <<과부하에 속성을 추가하는 것은 매우 번거로울 것 입니다.

이 예제는“기본적으로 nodiscard가있는 C ++ 17”언어에서 경고없이 관용적 C ++ 코드 컴파일을 만드는 것이 매우 지루하다는 것을 보여줍니다.

반짝이는 새로운 C ++ 17 코드 (이러한 속성을 사용할 수있는 곳)는 여전히 오래된 C ++ 표준을 대상으로하는 라이브러리와 함께 컴파일 될 수 있습니다. 이 이전 버전과의 호환성은 C / C ++ 에코 시스템에 중요합니다. 따라서 nodiscard를 기본값으로 설정하면 일반적인 관용적 사용 사례에 대한 많은 경고가 발생합니다.이 경고는 라이브러리의 소스 코드에 대한 광범위한 변경 없이는 해결할 수 없습니다.

논란의 여지가 있지만 여기서 문제는 의미의 변경이 아니라 각 C ++ 표준의 기능이 파일 단위가 아니라 컴파일 단위 단위 범위에 적용된다는 것입니다. 미래의 C ++ 표준이 헤더 파일에서 멀어지면 그러한 변화가 더 현실적입니다.


6
유용한 통찰력이 포함되어 있기 때문에 이것을 올렸습니다. 그래도 실제로 질문에 대답하는지 확실하지 않습니다. popor 와 같은 기능을 다시 사용 operator<<하면 내 가상 [[maydiscard]]속성 으로 명시 적으로 표시 되므로 경고가 생성되지 않습니다. 당신은 그러한 기능은 일반적으로 훨씬 더 있습니다 건가요 공통 난 내 자신의 코드베이스보다 믿고 싶은, 또는 훨씬 더 일반적인 일반적인 것보다? 기존 코드에 대한 첫 번째 단락은 확실히 사실이지만 다른 누군가가 현재 모든 코드 를와 함께 사용 [[nodiscard]]하고 있는지 아닌지 궁금해합니다.
Christian Hackl

23
@ChristianHackl : C ++의 역사에는 값을 const로 반환하는 것이 좋은 습관으로 여겨지는 시간이있었습니다. 당신은 말할 수 없습니다 returns_an_int() = 0, 왜 returns_a_str() = ""작동 해야 합니까? C ++ 11은 이것이 실제로 끔찍한 아이디어라는 것을 보여주었습니다. 이제 모든 코드는 값이 const이기 때문에 이동을 금지했기 때문입니다. 나는 그 교훈에서 적절하게 탈취하는 것이 "발신자가 당신의 결과로하는 일에 달려 있지 않다"고 생각합니다. 결과를 무시하는 것은 호출자가 원할 수있는 완벽하게 유효한 일이며, 그것이 잘못되었다는 것을 알지 못한다면 (차단 점이 높으면) 차단해서는 안됩니다.
GManNickG

13
empty()이름이 매우 모호하기 때문에 nodiscard on 도 의미가 있습니다. 술어 입니까 is_empty(), 아니면 돌연변이 clear()입니까? 일반적으로 이러한 뮤 테이터가 아무것도 반환하지 않을 것으로 예상되므로 반환 값에 대한 경고는 프로그래머에게 문제를 경고 할 수 있습니다. 보다 명확한 이름으로이 속성은 필요하지 않습니다.
amon

13
@ChristianHackl : 다른 사람들이 말했듯이 순수한 함수는 이것이 사용될 때의 좋은 예라고 생각합니다. 한 걸음 더 나아가 [[pure]]속성 이 있어야한다고 말하고 의미 [[nodiscard]]합니다. 그렇게하면 왜이 결과를 버리지 않아야 하는지 분명 합니다. 그러나 순수한 함수 외에는이 동작을 가져야하는 다른 클래스의 함수를 생각할 수 없습니다. 그리고 대부분의 기능은 순수하지 않으므로 기본적으로 nodiscard가 올바른 선택이라고 생각하지 않습니다.
GManNickG

5
@GManNickG : 시도와 오류 코드를 무시 코드를 디버깅에 실패하는 데, 나는 강하게 오류 코드의 대부분은 기본적으로 선택해야합니다 생각합니다.
Mooing Duck

9

내가 [[nodiscard]]거의 어디에도 없는 이유 :

  1. (주 :이 소개 할 방법 내 헤더에 너무 많은 소음을.
  2. (주요 :) 다른 사람들의 코드에 대해 강력한 추측을 가정해야한다고 생각하지 않습니다. 내가 준 반환 값을 버리고 싶습니까? 알았어.
  3. (부 :) C ++ 14와의 비 호환성을 보장합니다

이제 기본값으로 설정하면 모든 라이브러리 개발자가 모든 사용자가 강제로 반환 값을 무시하지 않도록해야합니다. 그것은 끔찍할 것입니다. 또는 당신은 그것들이 [[maydiscard]]무수히 많은 기능 을 추가하도록 강요합니다 . 그것은 또한 끔찍합니다.


0

예를 들어 operator <<에는 호출에 따라 반환 값이 반드시 필요하거나 전혀 쓸모가 없습니다. (std :: cout << x << y, 첫 번째는 스트림을 반환하기 때문에 필요하고 두 번째는 전혀 사용되지 않습니다). 이제 모든 사람들이 오류 검사를 위해 반환 값을 버리는 printf와 비교하지만 연산자 <<에는 오류 검사가 없으므로 처음에는 그다지 유용 할 수 없습니다. 따라서 두 경우 모두 nodiscard가 손상 될 수 있습니다.


이것은 묻지 않은 질문, 즉 " [[nodiscard]]모든 곳에서 사용하지 않는 이유는 무엇입니까 ?"입니다.
Christian Hackl
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.