C ++ 코드는 C ++ 03 및 C ++ 11에서 모두 유효하지만 다른 작업을 수행 할 수 있습니까?


298

C ++ 코드가 C ++ 03 표준과 C ++ 11 표준 을 모두 준수 할 수는 있지만 컴파일되는 표준에 따라 다른 작업을 수행합니까?


26
auto이런 상황이 발생할 수 있다고 확신 합니다
OMGtechy

8
예. 한 가지 예는 >>템플릿에서 사용될 때입니다. 두 표준 모두에 대해 컴파일 할 수있는 상황을 생각해 낼 수 있습니다. 변경 사항을 쉽게 찾을 수있는 또 다른 방법은 초기화입니다.
chris

5
>> 상황에 대한 좋은 기사가 있습니다 : gustedt.wordpress.com/2013/12/15/…
chris

6
@ OMGtechy : 나는 이것을 일으킬 수 있다고 생각 하지 않습니다 auto. 오래된 의미에서 auto선언에는 형식 이름이 필요합니다. 새로운 의미에서는 유형 이름이 허용되지 않습니다.
Keith Thompson

2
어떻게 개방형입니까? 당신은 또 다른 질문을 통해이 질문에 대한 대답은 "예, 여기에 어떻게 예가 있는지"라고 지적했습니다. 당신이 지적한대로 질문에 대한 명확한 대답이 있습니다.
jalf

답변:


283

정답은 그렇습니다. 플러스 측면에는 다음이 있습니다.

  • 이전에 암시 적으로 복사 된 객체는 이제 가능하면 암시 적으로 이동합니다.

부정적인 측면에서 몇 가지 예가 표준 부록 C에 나와 있습니다. 긍정적 인 것보다 더 많은 부정적인 것이 있지만, 그들 각각은 발생할 가능성이 훨씬 적습니다.

문자열 리터럴

#define u8 "abc"
const char* s = u8"def"; // Previously "abcdef", now "def"

#define _x "there"
"hello "_x // Previously "hello there", now a user defined string literal

0의 타입 변환

C ++ 11에서는 리터럴 만 정수 널 포인터 상수입니다.

void f(void *); // #1
void f(...); // #2
template<int N> void g() {
    f(0*N); // Calls #2; used to call #1
}

정수 나누기와 모듈로 후 둥근 결과

C ++ 03에서 컴파일러는 0 또는 음의 무한대로 반올림되었습니다. C ++ 11에서는 0으로 반올림해야합니다.

int i = (-1) / 2; // Might have been -1 in C++03, is now ensured to be 0

중첩 된 템플릿 닫는 중괄호 사이의 공백 >> vs>>

전문화 또는 인스턴스화에서 >>대신 C ++ 03에서 오른쪽 이동으로 해석 될 수 있습니다. 이것은 기존 코드하지만 깰 가능성이 높습니다 : (에서 http://gustedt.wordpress.com/2013/12/15/a-disimprovement-observed-from-the-outside-right-angle-brackets/를 )

template< unsigned len > unsigned int fun(unsigned int x);
typedef unsigned int (*fun_t)(unsigned int);
template< fun_t f > unsigned int fon(unsigned int x);

void total(void) {
    // fon<fun<9> >(1) >> 2 in both standards
    unsigned int A = fon< fun< 9 > >(1) >>(2);
    // fon<fun<4> >(2) in C++03
    // Compile time error in C++11
    unsigned int B = fon< fun< 9 >>(1) > >(2);
}

운영자 new는 이제 다른 예외를 던질 수 있습니다std::bad_alloc

struct foo { void *operator new(size_t x){ throw std::exception(); } }
try {
    foo *f = new foo();
} catch (std::bad_alloc &) {
    // c++03 code
} catch (std::exception &) {
    // c++11 code
}

사용자 선언 소멸자에는 C ++ 11에 도입 된 주요 변경 사항 의 암시 적 예외 사양 예가 있습니다 .

struct A {
    ~A() { throw "foo"; } // Calls std::terminate in C++11
};
//...
try { 
    A a; 
} catch(...) { 
    // C++03 will catch the exception
} 

size() 컨테이너가 O (1)에서 실행되어야합니다.

std::list<double> list;
// ...
size_t s = list.size(); // Might be an O(n) operation in C++03

std::ios_base::failurestd::exception더 이상 직접 파생되지 않습니다

직접베이스 클래스는 새로운 반면 std::runtime_error그렇지 않습니다. 그러므로:

try {
    std::cin >> variable; // exceptions enabled, and error here
} catch(std::runtime_error &) {
    std::cerr << "C++11\n";
} catch(std::ios_base::failure &) {
    std::cerr << "Pre-C++11\n";
}

11
좋아요, +1 또 다른 하나는 사용자가 암시 적으로 지금 소멸자를 선언한다는 것입니다 noexecpt(true)그래서 throw지금 전화하는 소멸자 std::terminate. 그러나 나는 그러한 코드를 작성한 사람이 이것에 대해 행복하기를 바랍니다!
typ1232

4
그러나 std :: system_error 자체는 (간접적으로) std :: exception에서 파생되므로 catch (std::exception &)여전히 catch std::ios_base::failure합니다.
user2665887

@ user2665887 당신이 맞아요. 여전히 프로그램의 동작에 영향을 줄 수 있지만 지금은 최소한의 예를 생각할 수 없습니다.

4
나는 당신이 말하는 operator new것이 정확하기 때문에 매우 혼란 std::bad_array_new_length스럽지만 ( 지금 던질 수 있음 ), 당신의 예는 그것을 전혀 보여주지 않습니다. 표시하는 코드는 C ++ 03 및 C ++ 11 AFAIK에서 동일합니다.
Mooing Duck

2
list :: size의 반대쪽은 O (1)입니다. splice는 이제 O (n)입니다
Tony Delroy

55

이 기사후속 조치를 설명 합니다. 이 기사>> 는 C ++ 03에서 C ++ 11로 의미를 변경하여 여전히 컴파일 할 수 있는 방법에 대한 좋은 예입니다 .

bool const one = true;
int const two = 2;
int const three = 3;

template<int> struct fun {
    typedef int two;
};

template<class T> struct fon {
    static int const three = ::three;
    static bool const one = ::one;
};

int main(void) {
    fon< fun< 1 >>::three >::two >::one; // valid for both  
}

핵심 부분은의 라인 main인 표현식입니다.

C ++ 03에서 :

1 >> ::three = 0
=> fon< fun< 0 >::two >::one;

fun< 0 >::two = int
=> fon< int >::one

fon< int >::one = true
=> true

C ++ 11에서

fun< 1 > is a type argument to fon
fon< fun<1> >::three = 3
=> 3 > ::two > ::one

::two is 2 and ::one is 1
=> 3 > 2 > 1
=> (3 > 2) > 1
=> true > 1
=> 1 > 1
=> false

축하합니다. 동일한 표현에 대한 두 가지 결과가 있습니다. 물론 C ++ 03은 테스트 할 때 Clang이라는 경고 양식을 제시했습니다.


필요로하지 않는 이상한 typename위한 ::twoC ++ 03 버전
자히르

3
다른 표준에 따라 평가 true하거나 false다른 표준에 따라 평가를 내리는 것이 좋습니다. 어쩌면 우리는 그것을 기능 테스트로 사용할 수있을 것입니다 </ joke>
cmaster-reinstate monica

@zahir, 유형이 아니라 값입니다.
chris

글쎄, 적절한 cmdline 옵션은 이것에 대해 경고 warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]하지만 ( ), 모호한 ::연산자가 의미 를 어떻게 바꾸는 지에 대한 좋은 예 (글로벌 범위를 참조하거나 바로 앞에 서있는 것을 역 참조하는 것)
example

@example, 놀랍게도 GCC는 경고를하지만 Clang은 그렇지 않습니다.
chris

39

예, 동일한 코드로 인해 C ++ 03과 C ++ 11간에 다른 동작이 발생하는 변경 사항이 많이 있습니다. 시퀀싱 규칙의 차이로 인해 이전에 정의되지 않은 일부 동작이 잘 정의되는 등 흥미로운 변경이 발생합니다.

1. 이니셜 라이저 목록 내에서 동일한 변수의 여러 돌연변이

매우 흥미로운 코너 사례는 초기화 목록 내에서 동일한 변수의 여러 돌연변이를 예로들 수 있습니다.

int main()
{
    int count = 0 ;
    int arrInt[2] = { count++, count++ } ;

    return 0 ;
}

C ++ 03과 C ++ 11 둘 다 잘 정의되어 있지만 C ++ 03의 평가 순서는 지정되어 있지 않지만 C ++ 11에서는 나타나는 순서대로 평가됩니다 . 따라서 clangC ++ 03 모드에서 컴파일 하면 다음과 같은 경고가 표시됩니다 ( 실제 참조 ).

warning: multiple unsequenced modifications to 'count' [-Wunsequenced]

    int arrInt[2] = { count++, count++ } ;

                           ^        ~~

그러나 C ++ 11에서는 경고를 제공하지 않습니다 ( 실제 참조 ).

2. 새로운 시퀀싱 규칙은 i = ++ i + 1; C ++ 11에 잘 정의되어 있습니다.

C ++ 03 이후에 채택 된 새로운 시퀀싱 규칙은 다음을 의미합니다.

int i = 0 ;
i = ++ i + 1;

C ++ 11에서 더 이상 정의되지 않은 동작이 아닙니다 . 이는 결함 보고서 637 에서 다룹니다 . 시퀀싱 규칙 및 예제가 일치하지 않습니다.

3. 새로운 시퀀싱 규칙은 또한 ++++ i를 만든다; C ++ 11에 잘 정의되어 있습니다.

C ++ 03 이후에 채택 된 새로운 시퀀싱 규칙은 다음을 의미합니다.

int i = 0 ;
++++i ;

더 이상 C ++ 11에서 정의되지 않은 동작이 아닙니다.

4. 약간 더 현명한 서명 된 왼쪽-시프트

C ++ 11의 이후 초안에는 1 비트를 부호 비트로 또는 부호 비트로 이동시키는 정의되지 않은 동작을N3485 아래에 고정시킨 링크가 포함됩니다 . 이것도 결함 보고서 1457 에서 다룹니다 . Howard Hinnant는 스레드에서이 변경의 중요성에 대해 언급했습니다. C ++ 11에서 왼쪽 이동 (<<)이 음의 정수 정의되지 않은 동작입니까? .

5. constexpr 함수는 C ++ 11에서 컴파일 시간 상수 표현식으로 취급 될 수 있습니다

C ++ 11은 다음과 같은 constexpr 함수를 도입했습니다 .

constexpr 지정자는 컴파일시 함수 또는 변수의 값을 평가할 수 있음을 선언합니다. 그런 다음 컴파일 시간 상수 표현식 만 허용되는 경우 이러한 변수와 함수를 사용할 수 있습니다.

C ++ 03에는 constexpr 기능이 없지만 표준 라이브러리는 C ++ 11에서 constexpr 로 많은 함수를 제공하므로 constexpr 키워드 를 명시 적으로 사용할 필요가 없습니다 . 예를 들어 std :: numeric_limits :: min 입니다. 예를 들어 다음과 같이 다른 동작이 발생할 수 있습니다.

#include <limits>

int main()
{
    int x[std::numeric_limits<unsigned int>::min()+2] ;
}

clangC ++ 03에서 이것을 사용하면 x가변 길이 배열이 생겨 확장 이며 다음 경고가 생성됩니다.

warning: variable length arrays are a C99 feature [-Wvla-extension]
    int x[std::numeric_limits<unsigned int>::min()+2] ;
         ^

C ++ 11 std::numeric_limits<unsigned int>::min()+2에서는 컴파일 시간 상수 표현식이며 VLA 확장이 필요하지 않습니다.

6. C ++ 11에서는 소멸자를 위해 예외 사양이 암시 적으로 생성되지 않습니다.

C ++ 11에서 사용자 정의 소멸자는 제외 자noexcept(true) 에서 설명한대로 암시 적 사양을 가지고 있기 때문에 다음 프로그램을 의미합니다.

#include <iostream>
#include <stdexcept>

struct S
{
  ~S() { throw std::runtime_error(""); } // bad, but acceptable
};

int main()
{
  try { S s; }
  catch (...) {
    std::cerr << "exception occurred";
  } 
 std::cout << "success";
}

C ++ 11 std::terminate에서는 C ++ 03에서 호출 하지만 성공적으로 실행됩니다.

7. C ++ 03에서 템플릿 인수는 내부 연결을 가질 수 없습니다

이것은 std :: sort가 함수 내에 선언 된 Compare 클래스를 허용하지 않는 이유 에서 잘 설명되어 있습니다 . 따라서 다음 코드는 C ++ 03에서 작동하지 않아야합니다.

#include <iostream>
#include <vector>
#include <algorithm>

class Comparators
{
public:
    bool operator()(int first, int second)
    {
        return first < second;
    }
};

int main()
{
    class ComparatorsInner : public Comparators{};

    std::vector<int> compares ;
    compares.push_back(20) ;
    compares.push_back(10) ;
    compares.push_back(30) ;

    ComparatorsInner comparatorInner;
    std::sort(compares.begin(), compares.end(), comparatorInner);

    std::vector<int>::iterator it;
    for(it = compares.begin(); it != compares.end(); ++it)
    {
        std::cout << (*it) << std::endl;
    }
}

하지만 현재 clang는 사용하지 않는 경고와 C ++ 03 모드에서이 코드 수 있습니다 -pedantic-errors종류 구역질이다 플래그, 그것은 살아 볼 .

8. 여러 템플릿을 닫을 때 >>가 더 이상 잘못된 형식이 아닙니다.

사용 >>가까운 여러 템플릿에 더 이상 잘못 형성 없지만 C ++ 03과 C + 11에서 다른 결과 코드로 이어질 수 있습니다. 아래 예는 직각 괄호와 이전 버전과의 호환성 에서 가져온 것입니다 .

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

C ++ 03의 결과는 다음과 같습니다.

0
3

그리고 C ++ 11에서 :

0
0

9. C ++ 11은 std :: vector 생성자의 일부를 변경합니다

이 답변 에서 약간 수정 된 코드는 std :: vector 의 다음 생성자를 사용하는 것을 보여줍니다 .

std::vector<T> test(1);

C ++ 03 및 C ++ 11에서 다른 결과를 생성합니다.

#include <iostream>
#include <vector>

struct T
{
    bool flag;
    T() : flag(false) {}
    T(const T&) : flag(true) {}
};


int main()
{
    std::vector<T> test(1);
    bool is_cpp11 = !test[0].flag;

    std::cout << is_cpp11 << std::endl ;
}

10. 집계 초기화 프로그램의 변환 축소

C ++ 11에서는 집계 초기화 프로그램의 축소 변환이 잘못되어 gccC ++ 11에서 기본적으로 경고를 제공하지만 C ++ 11 및 C ++ 03에서 모두 허용 하는 것처럼 보입니다 .

int x[] = { 2.0 };

이에 대한 내용은 C ++ 11 표준 섹션 8.5.4 목록 초기화 단락 3 에서 다룹니다 .

객체의 목록 초기화 또는 T 유형의 참조는 다음과 같이 정의됩니다.

다음 글 머리 기호 ( 강조 강조 )를 포함합니다.

그렇지 않으면 T가 클래스 유형 인 경우 생성자가 고려됩니다. 적용 가능한 생성자가 열거되고 과부하 해결 (13.3, 13.3.1.7)을 통해 가장 적합한 생성자가 선택됩니다. 인수를 변환하기 위해 축소 변환 (아래 참조)이 필요한 경우 프로그램이 잘못 구성됩니다.

이것과 더 많은 인스턴스는 C ++ 표준 섹션 annex C.2 C ++ 및 ISO C ++ 2003 초안 에서 다룹니다 . 또한 다음이 포함됩니다.

  • 새로운 종류의 문자열 리터럴 [...] 구체적으로 R, u8, u8R, u, uR, U, UR 또는 LR이라는 매크로는 문자열 리터럴에 인접 해있을 때 확장되지 않지만 문자열 리터럴의 일부로 해석됩니다. . 예를 들어

    #define u8 "abc"
    const char *s = u8"def"; // Previously "abcdef", now "def"
  • 사용자 정의 리터럴 문자열 지원 [...] 이전 # 1은 두 개의 별도 전처리 토큰으로 구성되었으며 매크로 _x가 확장되었을 것입니다. 이 국제 표준에서 # 1은 단일 전처리 토큰으로 구성되므로 매크로가 확장되지 않습니다.

    #define _x "there"
    "hello"_x // #1
  • 정수 나누기를 사용하는 정수 / 및 % [...] 2003 코드의 결과에 반올림을 지정하면 결과가 0 또는 음의 무한대로 반올림되지만,이 국제 표준은 항상 결과를 0으로 반올림합니다.

  • size () 멤버 함수의 복잡성은 이제 일정합니다 [...] C ++ 2003을 준수하는 일부 컨테이너 구현은이 국제 표준의 지정된 size () 요구 사항을 준수하지 않을 수 있습니다. std :: list와 같은 컨테이너를보다 엄격한 요구 사항으로 조정하려면 호환되지 않는 변경이 필요할 수 있습니다.

  • std :: ios_base :: failure [...]의 기본 클래스 변경 std :: ios_base :: failure는 더 이상 std :: exception에서 직접 파생되지 않지만 이제 std :: system_error에서 파생됩니다. std :: runtime_error. std :: ios_base :: failure가 std :: exception에서 직접 파생 된 것으로 가정하는 유효한 C ++ 2003 코드는이 국제 표준에서 다르게 실행될 수 있습니다.


따라서 대부분의 예제는 이전에 정의되지 않은 동작이 이제 잘 정의되어 있다는 사실로 좁혀집니다.
MatthiasB

@ MatthiasB 2, 3 및 4는 이것에 관한 것이므로이 시점에서 더 이상 예제의 대다수가 아닙니다. 더 많은 정의되지 않은 동작 예제를 찾아서 더 많이 추가하면 더 작은 세트가 될 것입니다.
Shafik Yaghmour

글쎄, # 1 행동은 지정되지 않았으므로 정의되지 않은 행동으로 계산합니다 (적어도 c ++ 03에서는 특정 결과를 얻을 수는 없지만 c ++ 11에서는 가능합니다). C ++의 표준 확장. 하지만 당신 말이 맞아요 더 많이 찾을수록 두 표준에 정의되어 있지만 다른 결과를 생성하는 예제를 더 많이 찾을 수 있습니다.
MatthiasB

@MatthiasB 예, 지정되지 않은 동작과 정의되지 않은 동작 모두 바람직하지 않은 결과가 있습니다. 리눅스를 고려한 확장은 확장 은 실제 gcc 확장에 의존 해야하는 많은 gcc 확장에 의존 한다. 나는이 질문에 처음 대답했을 때 많은 예를 찾을 것으로 기대하지 않았습니다.
Shafik Yaghmour

35

잠재적으로 위험한 역 호환이 불가능한 변경 중 하나는 std::vector특히 초기 크기를 지정하는 과부하 와 같은 시퀀스 컨테이너의 생성자입니다 . C ++ 03에서는 기본 구성 요소를 복사하고 C ++ 11에서는 각 구성 요소를 기본 구성합니다.

이 예제를 고려하십시오 ( boost::shared_ptr유효한 C ++ 03이되도록 사용).

#include <deque>
#include <iostream>

#include "boost/shared_ptr.hpp"


struct Widget
{
  boost::shared_ptr<int> p;

  Widget() : p(new int(42)) {}
};


int main()
{
  std::deque<Widget> d(10);
  for (size_t i = 0; i < d.size(); ++i)
    std::cout << "d[" << i << "] : " << d[i].p.use_count() << '\n';
}

C ++ 03 라이브 예제

C ++ 11 라이브 예제

그 이유는 C ++ 03이 다음과 같이 "크기 및 프로토 타입 요소 지정"과 "크기 만 지정"모두에 대해 하나의 과부하를 지정했기 때문입니다.

container(size_type size, const value_type &prototype = value_type());

이것은 항상 prototype컨테이너 size시간 으로 복사 됩니다 . 하나의 인수로 호출 size하면 기본 구성 요소의 사본이 작성됩니다 .

C ++ 11에서는이 생성자 서명이 제거되어 다음 두 가지 오버로드로 대체되었습니다.

container(size_type size);

container(size_type size, const value_type &prototype);

두 번째는 이전과 같이 작동 size하여 prototype요소의 사본을 만듭니다 . 그러나 첫 번째 것은 (크기 인수 만 지정하여 호출을 처리) 기본적으로 각 요소를 개별적으로 구성합니다.

이 변경의 이유는 C ++ 03 오버로드를 이동 전용 요소 유형으로 사용할 수 없기 때문입니다. 그러나 그것은 결코 획기적인 변화가 아니며, 하나는 거의 문서화되지 않았습니다.


3
이것이 명백한 변화이지만 C ++ 11 동작을 선호합니다. 나는 이것이 deque동일한 리소스를 공유하는 10 개의 위젯이 아닌 10 개의 개별 위젯을 보유하게 될 것으로 기대 합니다.
Agentlien

19

에서 읽지 못한 결과 std::istream가 변경되었습니다. CppReference 는 다음과 같이 요약합니다.

추출에 실패한 경우 (예 : 숫자가 필요한 곳에 문자를 입력 한 경우) value수정되지 않은 상태 failbit로 설정됩니다.(C ++ 11까지)

추출에 실패 할 경우, 0에 기록 valuefailbit설정됩니다. 너무 크거나 너무 작은 값의 추출 결과에 맞는 경우 value, std::numeric_limits<T>::max()또는 std::numeric_limits<T>::min()기록되고 failbit플래그가 설정된다. (C ++ 11부터)

이것은 새로운 시맨틱에 익숙하고 C ++ 03을 사용하여 작성해야하는 경우 주로 문제입니다. 다음은 특히 좋은 습관은 아니지만 C ++ 11에서 잘 정의 된 것입니다.

int x, y;
std::cin >> x >> y;
std::cout << x + y;

그러나 C ++ 03에서 위의 코드는 초기화되지 않은 변수를 사용하므로 정의되지 않은 동작이 있습니다.


4
C ++ 03에서는이 표준화 된 동작 을 사용하여과 같이 기본값을 제공 할 수 있다고 덧붙일 수 있습니다 int x = 1, y = 1; cin >> x >> y; cout << x*y;. C ++ 03을 사용하면 읽을 수 x없을 때 올바르게 생성 됩니다 y.
cmaster-monica reinstate

15

이 스레드 런타임에 C ++ 03과 C ++ 0x 간의 차이점을 발견 할 수있는 차이점은 예를 들어 C ++ 11 참조 축소를 활용하여 언어 차이를 판별하는 예제 (해당 스레드에서 복사)가 있습니다.

template <class T> bool f(T&) {return true; } 
template <class T> bool f(...){return false;} 

bool isCpp11() 
{
    int v = 1;
    return f<int&>(v); 
}

c ++ 11은 로컬 유형을 템플릿 매개 변수로 허용합니다.

template <class T> bool cpp11(T)  {return true;} //T cannot be a local type in C++03
                   bool cpp11(...){return false;}

bool isCpp0x() 
{
   struct local {} var; //variable with local type
   return cpp11(var);
}

7

또 다른 예는 다음과 같습니다.

#include <iostream>

template<class T>
struct has {
  typedef char yes;
  typedef yes (&no)[2];    
  template<int> struct foo;    
  template<class U> static yes test(foo<U::bar>*);      
  template<class U> static no  test(...);    
  static bool const value = sizeof(test<T>(0)) == sizeof(yes);
};

enum foo { bar };

int main()
{
    std::cout << (has<foo>::value ? "yes" : "no") << std::endl;
}

인쇄물:

Using c++03: no
Using c++11: yes

Coliru에서 결과 확인

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