페이지

2013년 8월 9일 금요일

C++ 개발자를 위한 Visual Studio 2013 - #1

이 글은 아래 글을 번역한 글임을 밝혀둡니다. 번역이 매끄럽지 않을 수도 있습니다. 잘못된 부분이 있다면 댓글로 남겨주세요.

원문 링크


6월부터 Visual Studio 2013 Preview ( VS12 ) 와 .NET Framework 4.5.1 이 사용가능 해졌다. 예상보다 빠른 릴리즈에도 불구하고, Visual Studio 2012보다 많은 향상된 기능들이 제공된다. 이 글에서 네이티브 개발을 위한 몇 가지 변화된 특징들을 소개 할 것이다. 더욱 자세한 내용은 여기 MSDN 에서 확인 할 수 있다. 또한 Visual Studio 2013은 여기 에서 다운로드 받을 수 있다.

C++

새로운 C++11과 C++14 compiler의 기능들이 있고 몇 가지 것들은 이미 사용가능 했었다. (참고: the November 2012 CTP for Visual Studio 2012 )

두 compiler들은 아래와 같은 기능들을 가지고 있다:

  • Compiler support for:
    • Default template arguments for function templates.
    • Delegating constructors.
    • Explicit conversion operators.
    • Initializer lists and uniform initialization.
    • Raw string literals.
    • Variadic templates.
    • rvalue/lvalue Reference Casts
  • library support for:
    • C++11 explicit conversion operators, initializer list, scoped enums and variadic templates
    • C++14 features:
      • Transparent operator functions
      • make_unique 
      • non-member functions cbegin()/cend()rbegin()/rend()crbegin()/crend()

위 내용들을 기반으로 몇가지 특징들을 살펴 볼 것이다. 하지만 모두는 아님. 왜냐면 지금까지 많은 논의가 되고 있으며 그러한 내용들의 추가적인 레퍼런스들은 직접 찾아 볼 수 있다.

Delegating constructors


C++ 11 에서는 생성자 클래스에서 같은 클래스 내의 생성자 함수를 초기화 리스트에서 호출하도록 할 수 있다.

#include <string>
#include <sstream>

int to_int(std::string const & s)
{
   std::stringstream sstr(s);
   int val;
   sstr >> val;
   return val;
}

class foo
{
   int value;
public:
   foo(std::string const & s) : foo(to_int(s)) {}
   foo(int a) : value(a) {}
};

만약 Visual Studio 2012에서 컴파일을 하게 된다면 아래와 같은 에러를 출력 할 것이다.

    error C2614: 'foo' : illegal member initialization: 'foo' is not a base or member

Explicit conversion operators


C++ 에서는 explicit 키워드를 표시 해줘서 하나의 argument 를 가지는 생성자를 막을 수 있다. 이 키워드는 implicit type 으로 operators 변환이 사용되지 않게 하기 위함이다. C++11에서는 이 키워드가 Implicit type의 operator 변환이 자신에게 직접 적용되지 않도록 할 수 있다.

아래 structure foo예제를 보면 int로의 operator 변환이 implicit 변환을 가능하게 한다는 것을 볼 수 있다.

struct foo
{
   operator int() { return 42; }
};

foo f;
int i = f; // OK -> i becomes 42

아래와 같이 explicit 키워드를 추가 했을때 변환이 불가능하다.
struct foo
{
   explicit operator int() { return 42; }
};

foo f;
int i = f; // error C2440

위의 할당은 아래의 에러 메시지를 출력한다.

    error C2440: 'initializing' : cannot convert from 'foo' to 'int' No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

그리고 explicit cast를 하도록 요구한다.
int i = static_cast<int>(f); // OK

Initializer list and uniform initialization

C++은 arrays 와 structs가 리스트로 초기화 되도록 했었다:
int arr [] = { 1, 2, 3, 4, 5 };

하지만 이는 클래스들에선 불가능 했었다. 아래와 같은 코드에선 적용되지 않는다:
std::vector<int> v = { 1, 2, 3, 4, 5 };

C++11에서는 std::initializer_list 를 소개했다. 이것은 초기화 리스트를 묶어주는 동일한 이름의 헤더파일내에 있는 템플릿 클래스이다. 이 템플릿 클래스는 생성자를 포함하는 함수들의 파라미터로 사용할 수 있다. 하나의 initializer_list 를 가지는 하나의 생성자는 다른 생성자 함수보다 우선적으로 수행된다. Visual Studio 2013에서는 연관된 모든 STL 타입들이 initializer_list 를 가지는 생성자로 업데이트 되어졌다. 이는 위와 같은 짓을 완전히 가능하게 해준다. 또한 생성자 뿐만 아니라 어떠한 함수에서도 initializer_list 사용이 가능하다.

  Collapse | Copy Code
int sum(std::initializer_list<int> list)
{
   int s = 0;
   for (auto const & i : list)
      s += i;

   return s;
}

auto s = sum({1, 1, 2, 3, 5, 8});

이런 uniform initialization 은 초기화 구문을 기반으로 한 단계 진보 시켜준다. 그리고 어떠한 타입 (arrays, classes, PODs, etc.)의 객체들도 초기화가 균일한 방법을 사용 되도록 해준다. 아래 코드를 보자:
class foo
{
   int x;
public:
   foo() : x(42) {}
   foo(int v) : x(v) {}
};

int i [] = { 42 };
foo f1(42);
foo f2;

객체들을 초기화 하기 위한 몇 가지 방법들:
  • {} 는 초기화 리스트를 위한
  • () 는 생성자 함수들의 호출을 위한
  • () 없이는 기본 생성자 함수의 호출을 위한
C++ 11에서는 모든 가능한 경우들에서 초기화 리스트 구문 사용이 가능하다. uniform initialization 과 다시 쓰여진 위의 샘플은 이와 같다:

int i [] { 42 };
foo f1 { 42 };
foo f2 {};

Raw string literals

기존에는 문자열 구문에서 몇 가지 문자들을 ( " ", \ ) 사용 할 때 빠져나오도록 요구했었다. C++11 에서는 "raw" 문자열 그대로 선언이 가능하다. 컴파일러가 이러한 문자들을 자동으로 빠져나오는 처리를 해준다. 이러한 raw string 구문은 R"( and suffixed with )" 와 같이 사용되어야 한다.



auto path = R"(c:\windows)";
auto quotedstring = R"(here are "quotes")";
auto multi = R"(this is the first line
               and here comes the second)";

만약 "( 또는 ")를 포함한 문자열을 원한다면? 그럼 "( ") 사이에 다른 delimiter, 16개의 문자까지를 사용할 수 있다. 예를 들어 "*( 와 )*" 로 시작하면 된다.
std::cout << R"*("(sample)")*" << std::endl;

make_unique


std::make_unique 타입의 객체(배열 포함)를 생성한다. 그리고 std::unique_ptr 객체내에서 그것을 감싼다. (간단히 말하면 unique_ptr 객체를 하나 리턴 시켜줌.)

auto p = std::make_unique<int>(42);

Transparent operator functions.

아래 샘플 코드를 보자:
int arr [] = {1,2,3,4,5};
std::sort(std::begin(arr), std::end(arr), std::greater<int>());

위의 의미는 std::greater 를 이용해 배열을 정렬하기 위한 것이다. 하지만 이것은 다소 장황하게 요소들의 타입을 필요로 한다. (논란이 일수 있는 다른 이슈들이 있음.)

그래서 "transparent" 연산자 함수들이 타입을 열거하기 위한 요구사항들을 제거하므로써 이것을 해결 해준다. Transparent는 둘다 완벽한 포워딩과 완벽한 리터닝을 의미한다.

std::sort(std::begin(arr), std::end(arr), std::greater<>());
std::map<int, std::string, std::greater<>> m;

이러한 functor들은 auto 와 decltype 함께 선언되어진다. 그리고 non-homogenous signatures (다형성: summerlight 님 제보) 와 함께.. 그래서 다른 타입들 ( 사과들과 복숭아들을 비교한다거나 미터와 초를 나누는거 같은... ) 과 함께 비교 또는 산술 연산자 내에서 사용되어질 수 있다.  std::greater 가 어떻게 정의되어 있는지 아래 예를 보자:
template <> struct greater<void> {
  template <class T, class U> auto operator()(T&& t, U&& u) const
    -> decltype(std::forward<T>(t) > std::forward<U>(u))
       { return std::forward<T>(t) > std::forward<U>(u); }
};


추가로 읽어 볼 것들:









댓글 3개:

  1. 맥락을 볼 때 non-homogenous signature라는 건 polymorphic functor에 대한 이야기로 보이네요.

    예를 들어 greater를 쓰면 함수 시그니쳐가(int, int)로 고정되어 두 타입이 다르거나 하는 경우 사용이 불가능하거나 의도치 않은 타입 변환이 일어날 수 있는데요...

    대신 polymorphic functor를 쓰면 컴파일러가 알아서 타입 추론을 해주므로 이런 문제도 없고 타입을 번잡하게 기술해줄 필요도 없게 됩니다.

    C++14에서 들어가는 [](auto&& lhs, auto&& rhs) { return lhs < rhs; } 이런 코드를 라이브러리로 지원한다고 생각하시면 될 듯...

    답글삭제
    답글
    1. 여기에서도 angle bracket이 짤리네요... greater<int> 이렇게 적어야 되나. ;

      삭제
    2. 아.. 그래서 non-homogenous 라는 표현이 사용된거군요. 이제 이해되네요~ 감사합니다..!

      삭제