C ++ 강력 형식 typedef


49

컴파일 단계에서 특정 유형의 버그를 잡기 위해 강력하게 유형이 지정된 typedef를 선언하는 방법을 생각했습니다. 종종 여러 유형의 ID로 int를 typedef하거나 위치 또는 속도에 대한 벡터를 typedef하는 경우가 종종 있습니다.

typedef int EntityID;
typedef int ModelID;
typedef Vector3 Position;
typedef Vector3 Velocity;

이것은 코드의 의도를 더 명확하게 만들 수 있지만, 코딩의 긴 밤 후에 다른 종류의 ID를 비교하거나 속도에 위치를 추가하는 것과 같은 바보 같은 실수를 할 수 있습니다.

EntityID eID;
ModelID mID;

if ( eID == mID ) // <- Compiler sees nothing wrong
{ /*bug*/ }


Position p;
Velocity v;

Position newP = p + v; // bug, meant p + v*s but compiler sees nothing wrong

불행히도, 강력하게 형식이 지정된 typedef에 대해 찾은 제안에는 boost를 사용하는 것이 포함되어 있습니다. 최소한으로는 가능하지 않습니다 (적어도 c ++ 11이 있습니다). 약간의 생각 후에, 나는이 아이디어에 도달했고, 누군가에 의해 그것을 실행하고 싶었습니다.

먼저 기본 유형을 템플릿으로 선언합니다. 그러나 템플릿 매개 변수는 정의에 사용되지 않습니다.

template < typename T >
class IDType
{
    unsigned int m_id;

    public:
        IDType( unsigned int const& i_id ): m_id {i_id} {};
        friend bool operator==<T>( IDType<T> const& i_lhs, IDType<T> const& i_rhs );
};

친구 함수는 실제로 클래스 정의 전에 전달 선언해야하며, 템플리트 클래스의 전달 선언이 필요합니다.

그런 다음 기본 유형에 대한 모든 멤버를 정의하고 템플리트 클래스임을 기억하십시오.

마지막으로 사용하고자 할 때 다음과 같이 typedef를 입력하십시오.

class EntityT;
typedef IDType<EntityT> EntityID;
class ModelT;
typedef IDType<ModelT> ModelID;

유형은 이제 완전히 분리되었습니다. 예를 들어 EntityID를 사용하는 함수는 ModelID를 대신 공급하려고하면 컴파일러 오류가 발생합니다. 기본 유형을 템플릿으로 선언해야 할뿐만 아니라 관련된 문제도 상당히 작아집니다.

누구든지이 아이디어에 대해 의견이나 비평을 받기를 바랐습니까?

예를 들어 위치와 속도의 경우 이것을 작성하는 동안 염두에 둔 한 가지 문제는 이전과 같이 유형을 자유롭게 변환 할 수 없다는 것입니다. 벡터에 스칼라를 곱하기 전에 다른 벡터를 얻을 수 있으므로 다음과 같이 할 수 있습니다.

typedef float Time;
typedef Vector3 Position;
typedef Vector3 Velocity;

Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };

Position newP = p + v*t;

강력하게 형식이 지정된 typedef를 사용하면 컴파일러가 시간별로 속도를 곱하면 위치가된다는 것을 알려 주어야합니다.

class TimeT;
typedef Float<TimeT> Time;
class PositionT;
typedef Vector3<PositionT> Position;
class VelocityT;
typedef Vector3<VelocityT> Velocity;

Time t = 1.0f;
Position p = { 0.0f };
Velocity v = { 1.0f, 0.0f, 0.0f };

Position newP = p + v*t; // Compiler error

이 문제를 해결하려면 모든 전환을 명시 적으로 전문화해야한다고 생각합니다. 반면에이 제한은 다른 종류의 오류를 방지하는 데 도움이 될 수 있습니다 (예 :이 영역에서는 의미가없는 거리에 속도에 거리를 곱하는 것). 그래서 나는 찢어졌고 사람들이 내 원래 문제에 대한 의견이나 해결 방법에 대해 의견이 있는지 궁금합니다.



같은 질문이 여기에 있습니다 : stackoverflow.com/q/23726038/476681
BЈовић

답변:


39

이들은 팬텀 유형 매개 변수 , 즉 표현에 사용되지 않고 유형이 다른 "공간"을 동일한 표현으로 분리하기 위해 사용되는 매개 변수화 된 유형의 매개 변수입니다.

그리고 공간에 관해 말하면, 팬텀 유형의 유용한 응용 프로그램입니다.

template<typename Space>
struct Point { double x, y; };

struct WorldSpace;
struct ScreenSpace;

// Conversions between coordinate spaces are explicit.
Point<ScreenSpace> project(Point<WorldSpace> p, const Camera& c) {  }

보시다시피, 단위 유형에는 몇 가지 어려움이 있습니다. 기본 컴포넌트에서 단위를 정수 지수로 구성된 벡터로 분해 할 수 있습니다.

template<typename T, int Meters, int Seconds>
struct Unit {
  Unit(const T& value) : value(value) {}
  T value;
};

template<typename T, int MA, int MB, int SA, int SB>
Unit<T, MA - MB, SA - SB>
operator/(const Unit<T, MA, SA>& a, const Unit<T, MB, SB>& b) {
  return a.value / b.value;
}

Unit<double, 0, 0> one(1);
Unit<double, 1, 0> one_meter(1);
Unit<double, 0, 1> one_second(1);

// Unit<double, 1, -1>
auto one_meter_per_second = one_meter / one_second;

여기서는 팬텀 값 을 사용하여 관련 단위의 지수에 대한 컴파일 타임 정보로 런타임 에 태그를 지정합니다. 속도, 거리 등을 위해 별도의 구조를 만드는 것보다 확장 성이 뛰어나고 사용 사례를 다루기에 충분할 수 있습니다.


2
흠, 작업에 유닛을 시행하기 위해 템플릿 시스템을 사용하는 것이 좋습니다. 그것을 생각하지 않았다, 감사합니다! 예를 들어 미터와 킬로미터 사이의 변환과 같은 것을 적용 할 수 있는지 궁금합니다.
키안

@Kian : SI 기본 단위 (m, kg, s, A, & c)를 내부적으로 사용하고 편의상 1km = 1000m의 별칭을 정의하면됩니다.
Jon Purdy

7

비슷한 값을 사용하여 일부 정수 값의 다른 의미를 구별하고 그 사이의 암시 적 변환을 금지했습니다. 나는 다음과 같은 일반적인 클래스를 작성했다.

template <typename T, typename Meaning>
struct Explicit
{
  //! Default constructor does not initialize the value.
  Explicit()
  { }

  //! Construction from a fundamental value.
  Explicit(T value)
    : value(value)
  { }

  //! Implicit conversion back to the fundamental data type.
  inline operator T () const { return value; }

  //! The actual fundamental value.
  T value;
};

물론 더 안전하고 싶다면 T생성자 explicit를 만들 수도 있습니다. 은 Meaning다음과 같이 사용된다 :

typedef Explicit<int, struct EntityIDTag> EntityID;
typedef Explicit<int, struct ModelIDTag> ModelID;

1
이것은 흥미롭지 만 충분히 강하지는 않습니다. typedefed 유형으로 함수를 선언하면 올바른 요소 만 매개 변수로 사용할 수 있습니다. 그러나 매번 다른 용도로 사용하면 매개 변수의 혼합을 막지 않고 구문상의 오버 헤드가 추가됩니다. 비교와 같은 작업을 말합니다. operator == (int, int)는 불만없이 EntityID와 ModelID를 취합니다 (명시 적으로 캐스팅해야하지만 잘못된 변수를 사용하지 못하게합니다).
키안

예. 제 경우에는 다른 종류의 ID를 서로에게 할당하지 못하게해야했습니다. 비교와 산술 연산은 나의 주요 관심사가 아니었다. 위의 구문은 할당을 금지하지만 다른 작업은 금지합니다.
mindriot

이것에 더 많은 에너지를 기꺼이 줄이려면 Explicit 클래스를 가장 일반적인 연산자로 감싸서 연산자를 처리하는 (공평한) 일반 버전을 만들 수 있습니다. 예제는 pastebin.com/FQDuAXdu 를 참조하십시오 . 랩퍼 클래스가 랩핑 된 연산자를 실제로 제공하는지 여부를 판별하려면 상당히 복잡한 SFINAE 구성이 필요합니다 ( 이 SO 질문 참조 ). 여전히 모든 경우를 다룰 수는 없으며 문제가되지 않을 수도 있습니다.
mindriot

문법적으로 우아하지만이 솔루션은 정수 유형에 대해 상당한 성능 저하를 초래합니다. 정수는 레지스터를 통해 전달 될 수 있으며 구조체 (단일 정수를 포함하더라도)는 할 수 없습니다.
Ghostrider

1

프로덕션 코드 (CS101 초보자와 같은 C ++ / 프로그래밍 초보자)에서 다음이 어떻게 작동하는지 잘 모르겠지만 C ++의 매크로 시스템을 사용하여 요리했습니다.

#define newtype(type_, type_alias) struct type_alias { \

/* make a new struct type with one value field
of a specified type (could be another struct with appropriate `=` operator*/

    type_ inner_public_field_thing; \  // the masked_value
    \
    explicit type_alias( type_ new_value ) { \  // the casting through a constructor
    // not sure how this'll work when casting non-const values
    // (like `type_alias(variable)` as opposed to `type_alias(bare_value)`
        inner_public_field_thing = new_value; } }

참고 : 귀하가 생각하는 함정 / 개선 사항을 알려주십시오.
Noein

1
원래 질문의 예와 같이이 매크로가 어떻게 사용되는지 보여주는 코드를 추가 할 수 있습니까? 그렇다면 큰 대답입니다.
Jay Elston
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.