C ++ gõ mạnh typedef


49

Tôi đã cố gắng nghĩ ra một cách tuyên bố các kiểu gõ mạnh, để bắt một loại lỗi nhất định trong giai đoạn biên dịch. Đó thường là trường hợp tôi nhập một int vào một số loại id hoặc một vectơ đến vị trí hoặc vận tốc:

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

Điều này có thể làm cho ý định của mã rõ ràng hơn, nhưng sau một đêm dài mã hóa, người ta có thể mắc những lỗi ngớ ngẩn như so sánh các loại id khác nhau, hoặc có thể thêm một vị trí vào vận tốc.

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

Thật không may, các đề xuất mà tôi đã tìm thấy cho các typedef được gõ mạnh bao gồm sử dụng boost, mà ít nhất với tôi không phải là một khả năng (ít nhất là tôi có c ++ 11). Vì vậy, sau một chút suy nghĩ, tôi nảy ra ý tưởng này và muốn điều hành nó bởi một ai đó.

Đầu tiên, bạn khai báo loại cơ sở là một mẫu. Tuy nhiên, tham số mẫu không được sử dụng cho bất kỳ định nghĩa nào:

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 );
};

Các hàm bạn bè thực sự cần phải được chuyển tiếp khai báo trước định nghĩa lớp, yêu cầu khai báo chuyển tiếp của lớp mẫu.

Sau đó, chúng tôi xác định tất cả các thành viên cho loại cơ sở, chỉ cần nhớ rằng đó là một lớp mẫu.

Cuối cùng, khi chúng tôi muốn sử dụng nó, chúng tôi đã gõ nó là:

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

Các loại bây giờ là hoàn toàn riêng biệt. Ví dụ, các hàm có EntityID sẽ gây ra lỗi trình biên dịch nếu bạn cố gắng cung cấp cho chúng ModelID. Ngoài việc phải khai báo các loại cơ sở dưới dạng mẫu, với các vấn đề đòi hỏi, nó cũng khá nhỏ gọn.

Tôi đã hy vọng bất cứ ai có ý kiến ​​hoặc phê bình về ý tưởng này?

Một vấn đề nảy sinh trong khi viết điều này, trong trường hợp vị trí và vận tốc chẳng hạn, là tôi không thể chuyển đổi giữa các loại một cách tự do như trước. Trường hợp trước khi nhân một vectơ với vô hướng sẽ cho một vectơ khác, vì vậy tôi có thể làm:

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;

Với kiểu gõ mạnh mẽ của tôi, tôi phải nói với trình biên dịch rằng việc đa tốc độ theo Thời gian sẽ dẫn đến một Vị trí.

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

Để giải quyết vấn đề này, tôi nghĩ rằng tôi phải chuyên môn hóa mọi chuyển đổi một cách rõ ràng, điều này có thể gây phiền toái. Mặt khác, giới hạn này có thể giúp ngăn ngừa các loại lỗi khác (giả sử, nhân Vận tốc với Khoảng cách, có lẽ, điều này sẽ không có ý nghĩa trong miền này). Vì vậy, tôi rất đau khổ và tự hỏi liệu mọi người có ý kiến ​​gì về vấn đề ban đầu của tôi hay cách tiếp cận của tôi để giải quyết nó.



câu hỏi tương tự ở đây: stackoverflow.com/q/23726038/476681
BЈовиЈ

Câu trả lời:


39

Đây là các tham số kiểu ảo , nghĩa là các tham số của loại tham số được sử dụng không phải để thể hiện chúng, mà để phân tách các không gian khác nhau của các loại khác nhau của các loại có cùng biểu diễn.

Và nói về không gian, đó là một ứng dụng hữu ích của các loại ma:

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) {  }

Tuy nhiên, như bạn đã thấy, có một số khó khăn với các loại đơn vị. Một điều bạn có thể làm là phân tách các đơn vị thành một vectơ số nguyên trên các thành phần cơ bản:

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;

Ở đây chúng tôi đang sử dụng các giá trị ảo để gắn thẻ các giá trị thời gian chạy với thông tin về thời gian biên dịch về số mũ trên các đơn vị liên quan. Điều này tốt hơn so với việc tạo các cấu trúc riêng biệt cho vận tốc, khoảng cách, v.v. và có thể đủ để trang trải trường hợp sử dụng của bạn.


2
Hmm, sử dụng hệ thống mẫu để thực thi các đơn vị trên các hoạt động là tuyệt vời. Không nghĩ về nó, cảm ơn! Bây giờ tôi đang tự hỏi nếu bạn có thể thực thi những thứ như chuyển đổi giữa mét và km chẳng hạn.
Kian

@Kian: Có lẽ bạn sẽ sử dụng các đơn vị cơ sở SI trong nội bộ.
Jon Purdy

7

Tôi đã có một trường hợp tương tự khi tôi muốn phân biệt ý nghĩa khác nhau của một số giá trị nguyên và cấm chuyển đổi ngầm giữa chúng. Tôi đã viết một lớp chung chung như thế này:

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ất nhiên nếu bạn muốn an toàn hơn nữa, bạn cũng có thể tạo ra hàm Ttạo explicit. Sau Meaningđó được sử dụng như thế này:

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

1
Điều này thật thú vị, nhưng tôi không chắc nó đủ mạnh. Nó sẽ đảm bảo rằng nếu tôi khai báo một hàm với kiểu gõ, chỉ các phần tử bên phải có thể được sử dụng làm tham số, điều này là tốt. Nhưng đối với mọi mục đích sử dụng khác, nó bổ sung thêm cú pháp cú pháp mà không ngăn cản việc trộn các tham số. Nói các hoạt động như so sánh. Toán tử == (int, int) sẽ lấy EntityID và ModelID mà không có khiếu nại (ngay cả khi rõ ràng yêu cầu tôi bỏ nó, nó không ngăn tôi sử dụng các biến sai).
Kian

Đúng. Trong trường hợp của tôi, tôi đã phải ngăn bản thân mình gán các loại ID khác nhau cho nhau. So sánh và hoạt động số học không phải là mối quan tâm chính của tôi. Cấu trúc trên sẽ cấm gán, nhưng không hoạt động khác.
mindriot

Nếu bạn sẵn sàng đưa thêm năng lượng vào việc này, bạn có thể xây dựng một phiên bản chung (khá) xử lý các toán tử, bằng cách làm cho lớp Explicit bọc các toán tử phổ biến nhất. Xem pastebin.com/FQDuAXdu để biết ví dụ - bạn cần một số cấu trúc SFINAE khá phức tạp để xác định xem lớp trình bao bọc có thực sự cung cấp các toán tử được bao bọc hay không (xem câu hỏi SO này ). Nhắc bạn, nó vẫn không thể bao gồm tất cả các trường hợp và có thể không đáng để gặp rắc rối.
mindriot

Mặc dù về mặt cú pháp, giải pháp này sẽ phải chịu hình phạt hiệu suất đáng kể cho các loại số nguyên. Số nguyên có thể được truyền qua các thanh ghi, các cấu trúc (thậm chí chứa một số nguyên) không thể.
Ghostrider

1

Tôi không chắc cách thức hoạt động sau đây trong mã sản xuất (Tôi là người mới bắt đầu lập trình C ++, như người mới bắt đầu CS101), nhưng tôi đã nấu nó bằng cách sử dụng các macro của 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; } }

Lưu ý: Xin vui lòng cho tôi biết bất kỳ cạm bẫy / cải tiến nào bạn nghĩ đến.
Noein

1
Bạn có thể thêm một số mã cho thấy cách sử dụng macro này - như trên các ví dụ trong câu hỏi ban đầu không? Nếu vậy, đây là một câu trả lời tuyệt vời.
Jay Elston
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.