Đi bao xa với các kiểu nguyên thủy typedef'ing như int


14

Tôi đã thấy mã C ++ như sau với nhiều typedefs.

Những lợi ích của việc sử dụng nhiều typedefs như thế này trái ngược với việc sử dụng các nguyên hàm C ++ là gì? Có một cách tiếp cận khác cũng có thể đạt được những lợi ích đó?

Cuối cùng, tất cả dữ liệu được lưu trữ trong bộ nhớ hoặc được truyền qua dây dưới dạng bit và byte, điều đó có thực sự quan trọng không?

types.h:

typedef int16_t Version;
typedef int32_t PacketLength;
typedef int32_t Identity;
typedef int32_t CabinetNumber;
typedef int64_t Time64;
typedef int64_t RFID;
typedef int64_t NetworkAddress;
typedef int64_t PathfinderAddress;
typedef int16_t PathfinderPan;
typedef int16_t PathfinderChannel;
typedef int64_t HandsetSerialNumber;
typedef int16_t PinNumber;
typedef int16_t LoggingInterval;
typedef int16_t DelayMinutes;
typedef int16_t ReminderDelayMinutes;
typedef int16_t EscalationDelayMinutes;
typedef float CalibrationOffset;
typedef float AnalogValue;
typedef int8_t PathfinderEtrx;
typedef int8_t DampingFactor;
typedef int8_t RankNumber;
typedef int8_t SlavePort;
typedef int8_t EventLevel;
typedef int8_t Percent;
typedef int8_t SensorNumber;
typedef int8_t RoleCode;
typedef int8_t Hour;
typedef int8_t Minute;
typedef int8_t Second;
typedef int8_t Day;
typedef int8_t Month;
typedef int16_t Year;
typedef int8_t EscalationLevel;

Có vẻ hợp lý để thử và đảm bảo cùng loại luôn được sử dụng cho một điều cụ thể để tránh tràn, nhưng tôi thường thấy mã trong đó "int" đã được sử dụng khá nhiều ở mọi nơi thay thế. Việc typedefing thường dẫn đến mã trông giống như thế này:

DoSomething(EscalationLevel escalationLevel) {
    ...
}

Điều đó khiến tôi tự hỏi mã thông báo nào thực sự mô tả tham số: loại tham số hoặc tên tham số?


2
IMHO, có vẻ như là một bài tập khá vô nghĩa, nhưng tôi chắc rằng một số người khác sẽ không đồng ý ...
Nim

1
Những loại này trông giống như tên biến.
Thuyền trưởng Hươu cao cổ

11
Lưu ý rằng điều này tạo ra ấn tượng rằng nó an toàn về kiểu, nhưng hoàn toàn không - typedefs chỉ tạo bí danh, nhưng không có gì ngăn bạn chuyển ví dụ Minuteđến một hàm có đối số được khai báo là kiểu Second.
Jesper

2
@Mark: nhìn nó theo cách khác. Nếu bạn mắc lỗi khi quyết định loại số nguyên hoặc các yêu cầu mới xuất hiện trong tương lai và vì vậy bạn muốn thay đổi nó, bạn có muốn thay đổi một typedef hay bạn muốn tìm kiếm mã cho mọi hàm thao tác trong một năm và thay đổi chữ ký của nó? 640k là đủ cho bất cứ ai, và tất cả những thứ đó. Nhược điểm tương ứng của typedef là mọi người vô tình hoặc cố ý viết mã dựa trên thực tế là Năm chính xác là 16 bit, sau đó nó thay đổi và mã của họ bị hỏng.
Steve Jessop

1
@Steve Jessop: Tôi không thể quyết định xem bạn nghĩ đó là ý tưởng tốt hay xấu :-) Phần đầu tiên dường như được ủng hộ, phần sau chống lại. Tôi đoán nó có ưu và nhược điểm sau đó.

Câu trả lời:


13

Tên của một tham số sẽ mô tả ý nghĩa của nó - trong trường hợp của bạn là mức độ leo thang. Kiểu là cách biểu thị giá trị - thêm typedefs như trong ví dụ của bạn làm xáo trộn phần này của chữ ký hàm, vì vậy tôi không khuyến nghị điều đó.

Typedefs hữu ích cho các mẫu hoặc nếu bạn muốn thay đổi loại được sử dụng cho một số tham số nhất định, ví dụ như khi di chuyển từ nền tảng 32 bit sang nền tảng 64 bit.


Đây dường như là sự đồng thuận chung sau đó. Vì vậy, nói chung bạn sẽ chỉ dính vào "int"? Tôi phải rất hiệu quả trong ứng dụng này khi chuyển dữ liệu, vì vậy đây có phải là trường hợp chỉ chuyển đổi sang int16_t (hoặc bất cứ loại đại diện lớn nhất nào cần cho một yếu tố cụ thể) trong quá trình tuần tự hóa không?

@Mark: Vâng, đó là cách bạn nên làm. Sử dụng typedefs để thể hiện kích thước của các kiểu dữ liệu đã sử dụng, nhưng không phân biệt cùng loại được sử dụng trong các ngữ cảnh khác nhau.
Bjorn Pollex

Cảm ơn - và chỉ cần làm rõ, bạn sẽ không bận tâm sử dụng int8_t thay vì int nói chung trong mã .. Tôi cho rằng lo lắng chính của tôi là một cái gì đó như "Danh tính" thực sự là một danh tính được tạo bởi cơ sở dữ liệu. Hiện tại, nó là 32 bit nhưng tôi không chắc liệu cuối cùng có thể trở thành 64 bit hay không. Ngoài ra, làm thế nào về int32_t vs int? int thường giống như int32_t, nhưng có thể không phải lúc nào tôi cũng đoán được trên một nền tảng khác? Tôi nghĩ rằng tôi chỉ nên bám vào "int" nói chung và "int64_t" khi cần thiết .. cảm ơn :-)

@Mark: Điều quan trọng về typedefs int32_tlà bạn phải đảm bảo chúng chính xác khi biên dịch trên các nền tảng khác nhau. Nếu bạn mong đợi phạm vi Identitythay đổi tại một số điểm, tôi nghĩ rằng tôi muốn thực hiện các thay đổi trực tiếp trong tất cả các mã bị ảnh hưởng. Nhưng tôi không chắc chắn, vì tôi sẽ cần biết thêm về thiết kế cụ thể của bạn. Bạn có thể muốn làm cho một câu hỏi riêng biệt.
Bjorn Pollex

17

Lúc đầu, tôi nghĩ "Tại sao không" nhưng sau đó tôi nhận ra rằng nếu bạn sẽ đi đến những chiều dài như vậy để tách các loại như thế, thì hãy sử dụng ngôn ngữ tốt hơn. Thay vì sử dụng bí danh, hãy xác định các loại:

class AnalogueValue
{
public:
    // constructors, setters, getters, etc..
private:
    float m_value;
};

Không có sự khác biệt về hiệu suất giữa:

typedef float AnalogueValue;
AnalogValue a = 3.0f;
CallSomeFunction (a);

và:

AnalogValue a (3.0f); // class version
CallSomeFunction (a);

và bạn cũng có những lợi thế của việc thêm xác nhận tham số và loại an toàn. Ví dụ: xem xét mã liên quan đến tiền bằng các loại nguyên thủy:

float amount = 10.00;
CallSomeFunction(amount);

Ngoài các vấn đề làm tròn, nó cũng cho phép bất kỳ loại nào có thể được chuyển đổi thành float:

int amount = 10;
CallSomeFunction(amount);

Trong trường hợp này, nó không phải là một vấn đề lớn, nhưng chuyển đổi ngầm có thể là một nguồn lỗi khó xác định. Sử dụng một typedefkhông giúp đỡ ở đây, vì chúng chỉ là một bí danh loại.

Sử dụng một loại hoàn toàn mới có nghĩa là không có chuyển đổi ngầm trừ khi bạn mã hóa toán tử truyền, đó là một ý tưởng tồi đặc biệt vì nó cho phép chuyển đổi ngầm định. Bạn cũng có thể đóng gói dữ liệu bổ sung:

class Money {
  Decimal amount;
  Currency currency;
};

Money m(Decimal("10.00"), Currency.USD);
CallSomeFunction(m);

Không có gì khác phù hợp với chức năng đó trừ khi chúng ta viết mã để thực hiện. Chuyển đổi tình cờ là không thể. Chúng tôi cũng có thể viết các loại phức tạp hơn khi cần mà không gặp nhiều rắc rối.


1
Bạn thậm chí có thể viết một số macro khủng khiếp để thực hiện tất cả việc tạo lớp này cho bạn. (Thôi nào, Skizz. Bạn biết bạn muốn.)

1
Đối với một số trong số này, thư viện đơn vị loại an toàn có thể hữu ích ( tuoml.sourceforge.net/html/scalar/scalar.html ), thay vì viết một lớp tùy chỉnh cho mỗi loại.
Steve Jessop

@Chris - Chắc chắn không phải là macro, nhưng có thể là một lớp mẫu. Như Steve chỉ ra, những lớp học đó đã được viết.
kevin cline

4
@Chris: Macro được gọi là BOOST_STRONG_TYPEDEFthực sự;)
Matthieu M.

3

Sử dụng typedefs cho các kiểu nguyên thủy như thế trông giống mã kiểu C.

Trong C ++, bạn sẽ nhận được các lỗi thú vị ngay khi bạn cố gắng quá tải các chức năng cho, nói EventLevelHour. Điều đó làm cho các tên loại thêm khá vô dụng.


2

Chúng tôi (tại công ty của chúng tôi) làm điều đó rất nhiều trong C ++. Nó giúp hiểu và duy trì mã. Điều này là tốt khi di chuyển mọi người giữa các đội hoặc thực hiện tái cấu trúc. Thí dụ:

typedef float Price;
typedef int64_t JavaTimestmap;

void f(JavaTimestamp begin, JavaTimestamp end, Price income);

Chúng tôi tin rằng đó là một cách thực hành tốt để tạo typedef cho tên thứ nguyên từ loại đại diện. Tên mới này thể hiện vai trò chung trong một phần mềm. Tên của một tham số là một vai trò cục bộ . Thích trong User sender, User receiver. Ở một số nơi, nó có thể là dư thừa void register(User user), nhưng tôi không coi đó là một vấn đề.

Sau này, người ta có thể có ý tưởng floatkhông phải là tốt nhất để đại diện cho giá vì các quy tắc làm tròn đặc biệt của đặt phòng, vì vậy một người tải xuống hoặc thực hiện loại BCDFloat(mã thập phân nhị phân) và thay đổi typedef. Không có tìm kiếm và thay thế công việc từ floatđể BCDFloatmà sẽ được làm cứng bằng một thực tế rằng có thể nhiều hơn nổi trong mã của bạn.

Nó không phải là một viên đạn bạc và có những cảnh báo riêng, nhưng chúng tôi nghĩ rằng sử dụng nó tốt hơn nhiều so với không.


Hoặc như Mathieu M. đã đề xuất tại bài viết của Skizz, người ta có thể đi như thế BOOST_STRONG_TYPEDEF(float, Price), nhưng tôi sẽ không đi xa đến thế trong một dự án trung bình. Hoặc có lẽ tôi sẽ. Tôi phải ngủ trên nó. :-)
Notinlist

1

typedefvề cơ bản cho phép bạn đưa ra một bí danh cho một type.
Nó cho phép bạn linh hoạt tránh việc type nameslặp đi lặp lại nhiều lần và làm cho bạn typedễ đọc hơn trong đó tên bí danh cho biết mục đích hoặc mục đích của type.

Đó là vấn đề được lựa chọn nhiều hơn nếu bạn muốn có nhiều tên dễ đọc hơn typedeftrong dự án của mình.
Thông thường, tôi tránh sử dụng typedeftrên các loại nguyên thủy, trừ khi chúng dài bất thường để được gõ. Tôi giữ tên tham số của tôi nhiều chỉ dẫn hơn.


0

Tôi sẽ không bao giờ làm một cái gì đó như thế này. Đảm bảo rằng tất cả chúng đều có cùng kích thước là một điều - nhưng bạn chỉ cần coi chúng là các loại tích phân.


0

Sử dụng typedefs như thế này là được, miễn là bất cứ ai sử dụng chúng đều không cần biết gì về biểu diễn cơ bản của chúng . Ví dụ, nếu bạn muốn vượt qua một PacketLengthđối tượng để một trong hai printfhoặc scanf, bạn sẽ cần phải biết loại thực tế của nó, do đó bạn có thể chọn các specifier chuyển đổi ngay. Trong trường hợp như vậy, typedef chỉ cần thêm một mức độ giấu giếm mà không mua lại bất cứ thứ gì; bạn cũng có thể vừa xác định đối tượng là int32_t.

Nếu bạn cần thực thi ngữ nghĩa cụ thể cho từng loại (chẳng hạn như phạm vi hoặc giá trị cho phép), thì tốt hơn hết bạn nên tạo một loại dữ liệu trừu tượng và các hàm để hoạt động trên loại đó, thay vì chỉ tạo một typedef.

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.