새로운 표준 버전을 사용하는 C ++에서 자동 동작 변경이 있었습니까?


104

(목록이 아닌 요점을 증명하기 위해 한두 가지 예를 찾고 있습니다.)

C ++ 표준의 변경 (예 : 98에서 11, 11에서 14 등)이 기존의 잘 구성되고 정의 된 동작 사용자 코드의 동작을 자동으로 변경 한 적이 있습니까? 즉, 최신 표준 버전으로 컴파일 할 때 경고 나 오류가 없습니까?

메모:

  • 구현 자 / 컴파일러 작성자 선택이 아니라 표준 필수 동작에 대해 질문하고 있습니다.
  • 인위적인 코드가 적을수록 더 좋습니다 (이 질문에 대한 답변).
  • .NET과 같은 버전 감지 코드를 의미하지는 않습니다 #if __cplusplus >= 201103L.
  • 메모리 모델과 관련된 답변은 괜찮습니다.

의견은 확장 된 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Samuel Liew

3
이 질문이 왜 종결되었는지 이해할 수 없습니다. " 새로운 표준 버전을 사용하여 C ++에서 조용한 동작 변경이 있었습니까? "완벽하게 초점을 맞춘 것처럼 보이며 질문의 본문이 그것에서 벗어나지 않는 것 같습니다.
Ted Lyngmo 20.08.14

제 생각에 가장 큰 침묵의 깨는 변화는의 재정의입니다 auto. C ++ 11 이전 auto x = ...;에는 int. 그 후 무엇이든 선언합니다 ....
Raymond Chen

@RaymondChen :이 변경은 암시 적으로 int를 정의하고 있었지만 명시 적으로 auto형식 변수를 명시한 경우에만 조용 합니다. 나는 당신이 아마 한 손에 모호한 C 코드 대회를 제외하고, 코드의 종류를 작성합니다 세계에있는 사람 수에 ... 셀 수 있다고 생각
einpoklum

사실, 그것이 그들이 그것을 선택한 이유입니다. 그러나 그것은 의미론의 큰 변화였습니다.
Raymond Chen

답변:


113

C ++ 17 string::data에서 const char*로 변경 의 반환 유형입니다 char*. 확실히 차이를 만들 수 있습니다.

void func(char* data)
{
    cout << data << " is not const\n";
}

void func(const char* data)
{
    cout << data << " is const\n";
}

int main()
{
    string s = "xyz";
    func(s.data());
}

약간 고안되었지만이 합법적 인 프로그램은 출력을 C ++ 14에서 C ++ 17로 변경합니다.


7
오, 나는 std::stringC ++ 17에 대한 변경 사항을 깨닫지 못했습니다 . 어쨌든 C ++ 11 변경으로 인해 어떻게 든 조용한 동작이 변경되었을 수 있다고 생각했을 것입니다. +1.
einpoklum

9
설득력이 있든 없든, 이것은 잘 구성된 코드의 변화를 아주 잘 보여줍니다.
David C. Rankin

제쳐두고, 변경은 아마도 char *에서 작동하는 레거시 함수를 통해 std :: string의 내용 을 현장에서 변경할 때 재미 있지만 합법적 인 사용 사례를 기반으로 합니다. 이제 완전히 합법적입니다. 벡터와 마찬가지로 조작 할 수있는 기본 연속 배열이 있다는 보장이 있습니다 (항상 반환 된 참조를 통해 할 수 있습니다. 이제는 더 자연스럽고 명시 적으로 만들어졌습니다). 가능한 사용 사례는 편집 가능한 고정 길이 데이터 세트 (예 : 일종의 메시지)이며 std :: container를 기반으로하는 경우 수명 관리, 복사 가능성 등과 같은 STL 서비스를 유지합니다.
Peter-Reinstate Monica

81

이 질문에 대한 대답 은 단일 size_type값을 사용하여 벡터를 초기화하면 C ++ 03과 C ++ 11간에 다른 동작 어떻게 발생하는지 보여줍니다 .

std::vector<Something> s(10);

C ++ 03은 기본 요소 유형의 임시 객체를 생성하고 해당 임시 Something요소로부터 벡터의 각 요소를 복사 생성합니다.

C ++ 11은 기본적으로 벡터의 각 요소를 구성합니다.

많은 (대부분?) 경우에 이러한 결과는 동등한 최종 상태가되지만 그럴 이유는 없습니다. Something의 기본 / 복사 생성자의 구현에 따라 다릅니다 .

이 인위적인 예를 참조하십시오 .

class Something {
private:
    static int counter;

public:
    Something() : v(counter++) {
        std::cout << "default " << v << '\n';
    }

    Something(Something const & other) : v(counter++) {
        std::cout << "copy " << other.v << " to " << v << '\n';
    }

    ~Something() {
        std::cout << "dtor " << v << '\n';
    }

private:
    int v;
};

int Something::counter = 0;

C ++ 03은 하나 Something를 기본 생성 하고 v == 0그 하나 를 10 개 더 복사 생성합니다. 마지막으로 벡터는 v값이 1에서 10까지 인 10 개의 객체를 포함합니다.

C ++ 11은 각 요소를 기본 구성합니다. 사본이 만들어지지 않습니다. 마지막으로 벡터에는 v값이 0에서 9까지 인 10 개의 객체가 포함됩니다.


@einpoklum 그래도 인위적인 예를 추가했습니다. :)
cdhowie

3
나는 그것이 인위적인 것이라고 생각하지 않는다. 다른 생성자는 종종 메모리 할당과 같은 작업에서 다르게 작동합니다. 한 부작용을 다른 부작용 (I / O)으로 교체했습니다.
einpoklum

17
@cdhowie 전혀 고안되지 않았습니다. 나는 최근에 UUID 수업에서 일하고있었습니다. 기본 생성자는 임의의 UUID를 생성했습니다. 이 가능성에 대해 전혀 몰랐고 C ++ 11 동작을 가정했습니다.
john

5
이것이 중요한 클래스의 널리 사용되는 실제 사례 중 하나는 OpenCV cv::mat입니다. 기본 생성자는 새 메모리를 할당하는 반면 복사 생성자는 기존 메모리에 새 뷰를 만듭니다.
jpa

나는 그것을 인위적인 예라고 부르지 않고 행동의 차이를 분명히 보여줍니다.
David Waterworth

51

표준은 Annex C [diff] 에서 주요 변경 사항 목록을 가지고 있습니다. 이러한 변화의 대부분은 조용한 행동 변화로 이어질 수 있습니다.

예 :

int f(const char*); // #1
int f(bool);        // #2

int x = f(u8"foo"); // until C++20: calls #1; since C++20: calls #2

7
@einpoklum 글쎄요, 적어도 12 개는 기존 코드의 "의미를 변경"하거나 "다르게 실행"하는 것으로 알려져 있습니다.
cpplearner

4
이 특정 변경에 대한 근거를 어떻게 요약 하시겠습니까?
Nayuki

4
@Nayuki는 bool버전을 사용 하는 것이 의도 된 변경 자체가 아니라 다른 변환 규칙의 부작용 일 뿐이라고 확신합니다 . 진짜 의도는 문자 인코딩 사이의 혼란의 일부를 중지하는 것, 실제 변화되고 있음을 u8리터럴 제공하는 데 사용 const char*하지만 지금 제공 const char8_t*.
leftaroundabout

25

표준 라이브러리에 새로운 메서드 (그리고 종종 함수)를 추가 할 때마다 이런 일이 발생합니다.

표준 라이브러리 유형이 있다고 가정합니다.

struct example {
  void do_stuff() const;
};

아주 간단합니다. 일부 표준 개정에서는 새 메서드 또는 오버로드 또는 그 옆에 추가됩니다.

struct example {
  void do_stuff() const;
  void method(); // a new method
};

이는 기존 C ++ 프로그램의 동작을 자동으로 변경할 수 있습니다.

이는 C ++의 현재 제한된 리플렉션 기능이 이러한 메서드가 있는지 감지 하고이를 기반으로 다른 코드를 실행 하기에 충분하기 때문입니다.

template<class T, class=void>
struct detect_new_method : std::false_type {};

template<class T>
struct detect_new_method< T, std::void_t< decltype( &T::method ) > > : std::true_type {};

이것은 새로운 것을 감지하는 비교적 간단한 방법 일 뿐이며 method무수히 많은 방법이 있습니다.

void task( std::false_type ) {
  std::cout << "old code";
};
void task( std::true_type ) {
  std::cout << "new code";
};

int main() {
  task( detect_new_method<example>{} );
}

클래스에서 메서드를 제거 할 때도 마찬가지입니다.

이 예제는 메소드의 존재를 직접 감지하지만 간접적으로 발생하는 이런 종류의 일은 덜 인위적 일 수 있습니다. 구체적인 예로, 반복 가능한지 또는 원시 바이트를 가리키는 데이터와 크기 멤버가 있는지 여부에 따라 컨테이너로 직렬화 할 수 있는지 여부를 결정하는 직렬화 엔진이있을 수 있습니다. 다른 하나.

표준은 .data()컨테이너에 메소드를 추가 하고 갑자기 유형이 직렬화에 사용하는 경로를 변경합니다.

C ++ 표준이 할 수있는 모든 것은 동결을 원하지 않는다면 조용히 깨지는 종류의 코드를 드물거나 비합리적으로 만드는 것입니다.


3
SFINAE를 제외 할 수있는 질문에 자격을 부여 했어야했습니다. 이것은 제가 의미하는 바가 아니기 때문입니다.하지만 예, 사실입니다. +1.
einpoklum

"간접적으로 일어나는 이런 일들"은 실제 함정이기 때문에 반대표가 아닌 찬성표로 이어졌습니다.
Ian Ringrose 20.08.08

1
이것은 정말 좋은 예입니다. OP가 그것을 배제하려고했지만 이것은 아마도 기존 코드에 자동 동작 변경을 일으킬 가능성 이 가장 높은 것 중 하나 일 것입니다. +1
cdhowie 20.08.09

1
@TedLyngmo 탐지기를 고칠 수 없다면 탐지 된 것을 변경하십시오. 텍사스 명사수!
Yakk-Adam Nevraumont

15

오 이런 ... 제공된 링크 cpplearner무섭습니다 .

무엇보다도 C ++ 20은 C ++ 구조체의 C 스타일 구조체 선언을 허용하지 않았습니다.

typedef struct
{
  void member_foo(); // Ill-formed since C++20
} m_struct;

당신이 그런 구조체를 작성하는 것을 배웠다면 (그리고 "C with classes"를 가르치는 사람들은 정확히 그것을 가르친다) 당신은 망했다 .


20
누구든지 칠판에 "구조체를 typedef하지 않을 것"이라고 100 번 써야한다고 가르친 사람입니다. C, imho로도 안됩니다. 어쨌든, 그 변경 사항은 조용하지 않습니다. 새로운 표준에서 "유효한 C ++ 2017 코드 (익명, 비 C 구조체에서 typedef 사용)는 형식이 잘못되었을 수 있습니다.""형식이 잘못되었습니다. 프로그램에 구문 오류 또는 진단 가능한 의미 오류가 있습니다. . 진단을 실행하려면 적합한 C ++ 컴파일러가 필요합니다 . " .
Peter-Monica 복원

19
@ Peter-ReinstateMonica 글쎄, 나는 항상 typedef내 구조를 가지고 있으며, 나는 그것에 내 분필을 낭비하지 않을 것입니다. 이것은 가장 확실히 취향의 문제이며, 당신의 관점을 공유하는 영향력있는 사람들 (Torvalds ...)이 있지만 저와 같은 다른 사람들은 유형에 대한 명명 규칙이 필요한 전부라고 지적 할 것입니다. struct키워드로 코드를 어지럽히면 대문자 ( MyClass* object = myClass_create();)가 전달하지 않는다는 이해가 거의 추가 되지 않습니다. 나는 당신이 struct당신의 코드 를 원한다면 그것을 존중합니다 . 그러나 나는 그것을 원하지 않는다.
cmaster-모니카 복원

5
즉, C ++를 프로그래밍 할 때 struct일반 오래된 데이터 유형과 class멤버 함수가있는 모든 유형에만 사용하는 것이 참으로 좋은 관례 입니다. 더있다 그러나 당신은 C에서이 규칙을 사용 할 수 없습니다 classC.에
cmaster - 분석 재개 모니카

1
@ Peter-ReinstateMonica 예, C에서 구문 론적으로 메소드를 첨부 할 수는 없지만 C struct가 실제로 POD 라는 의미는 아닙니다 . 내가 C 코드를 작성하는 방식에서 대부분의 구조는 단일 파일의 코드와 클래스 이름을 전달하는 함수에 의해서만 처리됩니다. 기본적으로 구문 설탕이없는 OOP입니다. 이를 통해 실제로에서 변경되는 내용 struct과 멤버간에 보장되는 불변을 제어 할 수 있습니다 . 그래서 structs저는 멤버 함수, 개인 구현, 불변 및 데이터 멤버의 추상을 갖는 경향이 있습니다. POD처럼 들리지 않습니까?
cmaster-모니카 복원

6
extern "C"블록에서 금지되지 않는 한 ,이 변경 사항에 아무런 문제가 없습니다. 아무도 C ++에서 구조체를 typedefing해서는 안됩니다. 이것은 C ++가 Java와 다른 의미를 가지고 있다는 사실보다 더 큰 장애물이 아닙니다. 새로운 프로그래밍 언어를 배울 때 몇 가지 새로운 습관을 배워야 할 수도 있습니다.
Cody Gray

15

다음은 C ++ 03에서는 3을 인쇄하고 C ++ 11에서는 0을 인쇄하는 예입니다.

template<int I> struct X   { static int const c = 2; };
template<> struct X<0>     { typedef int c; };
template<class T> struct Y { static int const c = 3; };
static int const c = 4;
int main() { std::cout << (Y<X< 1>>::c >::c>::c) << '\n'; }

이러한 동작 변경은에 대한 특수 처리로 인해 발생했습니다 >>. C ++ 11 이전에는 >>항상 오른쪽 시프트 연산자였습니다. C ++ 11 >>을 사용하면 템플릿 선언의 일부가 될 수도 있습니다.


글쎄요, 기술적으로 이것은 사실입니다.하지만이 코드는 >>그 방법을 사용했기 때문에 시작하기에 "비공식적으로 모호했습니다" .
einpoklum

11

트라이 그래프가 떨어졌습니다.

소스 파일은 구현 정의 방식으로 표준에 정의 된 소스 문자 집합에 매핑 되는 물리적 문자 집합 으로 인코딩됩니다 . 기본적으로 소스 문자 집합에 필요한 모든 구두점이없는 일부 물리적 문자 집합의 매핑을 수용하기 위해 언어는 덜 일반적인 구두점 문자 대신 사용할 수있는 세 개의 일반적인 문자 시퀀스 인 삼중 문자를 정의했습니다. 이를 처리하려면 전처리 기와 컴파일러가 필요했습니다.

C ++ 17에서는 삼중 문자가 제거되었습니다. 따라서 일부 소스 파일은 물리적 문자 집합에서 소스 문자 집합에 일대일로 매핑되는 다른 물리적 문자 집합으로 먼저 변환되지 않는 한 최신 컴파일러에서 허용되지 않습니다. (실제로 대부분의 컴파일러는 삼중 문자의 해석을 선택 사항으로 설정했습니다.) 이것은 미묘한 동작 변경은 아니지만 이전에 허용 된 소스 파일이 외부 번역 프로세스없이 컴파일되는 것을 방지하는 주요 변경 사항입니다.

더 많은 제약 char

표준은 또한 구현이 정의 된 실행 문자 집합을 참조 하지만 최소한 전체 소스 문자 집합과 적은 수의 제어 코드를 포함해야합니다.

char실행 문자 집합의 모든 값을 효율적으로 표현할 수있는 부호없는 정수 형식으로 정의 된 C ++ 표준 입니다. 언어 변호사의 표현을 통해 a char는 8 비트 이상이어야 한다고 주장 할 수 있습니다 .

구현에서에 대해 부호없는 값을 사용하는 경우 char0에서 255까지의 범위가 될 수 있으므로 가능한 모든 바이트 값을 저장하는 데 적합합니다.

그러나 구현에서 부호있는 값을 사용하는 경우 옵션이 있습니다.

대부분은 2의 보수를 사용 char하여 -128에서 127 사이의 최소 범위를 제공 합니다. 이는 256 개의 고유 한 값입니다.

그러나 또 다른 옵션은 부호 + 크기로, 한 비트는 숫자가 음수인지 여부를 나타 내기 위해 예약되고 다른 7 비트는 크기를 나타냅니다. 이는 char-127에서 127까지의 범위를 제공 하며, 이는 255 개의 고유 값에 불과합니다. (0을 나타내는 유용한 비트 조합 하나를 잃기 때문입니다.)

나는 확실히위원회는 지금까지 명시 적 결함으로이 지정 아니지만, 당신은에서 왕복을 보장하기 위해 표준에 의존 할 수 없기 때문에 그것은이었다 unsigned charchar다시 원래 값을 유지한다. (실제로 모든 구현은 부호있는 정수 유형에 대해 모두 2의 보수를 사용했기 때문에 수행되었습니다.)

최근에야 (C ++ 17?) 라운드 트립을 보장하기 위해 문구가 수정되었습니다. 에 대한 다른 모든 요구 사항과 함께이 수정은 명시 적으로 말하지 않고 char부호에 대한 2의 보수를 효과적으로 요구 char합니다 (표준이 다른 부호있는 정수 유형에 대해 부호 + 크기 표현을 계속 허용하더라도). 모든 부호있는 정수 유형이 2의 보수를 사용하도록 요구하는 제안이 있지만 C ++ 20으로 만들 었는지 기억 나지 않습니다.

따라서 이것은 이전에 잘못된 지나치게 주제 넘은 코드에 소급 수정을 제공하기 때문에 찾고있는 것과 반대입니다 .


삼중 문자 부분은이 질문에 대한 답이 아닙니다. 그것은 조용한 변화가 아닙니다. 그리고 IIANM, 두 번째 부분은 구현 정의를 엄격히 요구하는 동작으로 변경 한 것입니다.
einpoklum

10

이것이 올바른 코드에 대한 주요 변경 사항이라고 생각할지 모르겠지만 ...

C ++ 11 이전에는 컴파일러가 복사 생성자에 관찰 가능한 부작용이있는 경우에도 특정 상황에서 복사본을 제거하도록 허용되었지만 필수는 아닙니다. 이제 우리는 복사 제거를 보장했습니다. 동작은 기본적으로 구현 정의에서 필수로 바뀌 었습니다.

이는 복사 생성자 부작용 이 이전 버전에서 발생했을 있지만 최신 버전에서는 발생 하지 않음을 의미 합니다. 올바른 코드가 구현 정의 결과에 의존해서는 안된다고 주장 할 수 있지만, 그러한 코드가 올바르지 않다고 말하는 것과 완전히 같지는 않다고 생각합니다.


1
이 "요구 사항"이 C ++ 11이 아닌 C ++ 17에 추가되었다고 생각 했습니까? ( 임시 구체화 참조 )
cdhowie

@cdhowie : 나는 당신이 옳다고 생각합니다. 이 글을 썼을 때 나는 표준이 없었고 아마도 내 검색 결과 중 일부를 너무 많이 신뢰했을 것입니다.
Adrian McCarthy

구현 정의 동작에 대한 변경은이 질문에 대한 답변으로 간주되지 않습니다.
einpoklum

7

스트림에서 (숫자) 데이터를 읽고 읽기가 실패 할 때의 동작이 C ++ 11 이후로 변경되었습니다.

예를 들어 정수를 포함하지 않는 스트림에서 정수를 읽는 경우 :

#include <iostream>
#include <sstream>

int main(int, char **) 
{
    int a = 12345;
    std::string s = "abcd";         // not an integer, so will fail
    std::stringstream ss(s);
    ss >> a;
    std::cout << "fail = " << ss.fail() << " a = " << a << std::endl;        // since c++11: a == 0, before a still 12345 
}

C ++ 11은 실패 할 때 읽기 정수를 0으로 설정하기 때문에; c ++ <11에서 정수는 변경되지 않았습니다. 즉, gcc는 표준을 C ++ 98로 다시 강제 적용하더라도 (-std = c ++ 98 사용) 항상 버전 4.4.7 이후로 새로운 동작을 보여줍니다.

(아무것도 읽을 수 없는데도 그 자체로 유효한 값을 0으로 변경하는 이유는 무엇입니까?)

참조 : https://en.cppreference.com/w/cpp/locale/num_get/get 참조


그러나 returnType에 대해 언급 된 변경 사항은 없습니다. 만 2 소식은 C ++ 11부터 사용할 수 과부하
성공 빌드

이것이 C ++ 98과 C ++ 11 모두에서 정의 된 동작입니까? 아니면 행동이 정의 되었습니까?
einpoklum

cppreference.com이 맞으면 "오류가 발생하면 v는 변경되지 않습니다. (C ++ 11까지)"따라서 동작은 C ++ 11 이전에 정의되어 변경되었습니다.
DanRechtsaf

내 이해로는 ss> a의 동작이 실제로 정의되었지만 초기화되지 않은 변수를 읽는 매우 일반적인 경우에 대해 C ++ 11 동작은 정의되지 않은 동작 인 초기화되지 않은 변수를 사용합니다. 따라서 오류에 대한 기본 구성은 매우 일반적인 정의되지 않은 동작을 방지합니다.
Rasmus Damgaard Nielsen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.