typedefs và #defines


32

Tất cả chúng ta chắc chắn đã sử dụng typedefs và #defines lần này hay lần khác. Hôm nay trong khi làm việc với họ, tôi bắt đầu suy nghĩ về một điều.

Hãy xem xét 2 tình huống dưới đây để sử dụng intkiểu dữ liệu với tên khác:

typedef int MYINTEGER

#define MYINTEGER int

Giống như tình huống trên, trong nhiều tình huống, chúng ta có thể hoàn thành rất tốt việc sử dụng #define và cũng thực hiện tương tự bằng cách sử dụng typedef, mặc dù cách chúng ta làm tương tự có thể hoàn toàn khác nhau. #define cũng có thể thực hiện các hành động MACRO mà typedef không thể.

Mặc dù lý do cơ bản để sử dụng chúng là khác nhau, làm việc của chúng khác nhau như thế nào? Khi nào nên ưu tiên hơn cái kia khi cả hai có thể được sử dụng? Ngoài ra, một trong những đảm bảo là nhanh hơn so với người khác trong tình huống nào? (ví dụ #define là chỉ thị tiền xử lý, vì vậy mọi thứ được thực hiện sớm hơn so với lúc biên dịch hoặc thời gian chạy).


8
Sử dụng #define những gì chúng được thiết kế cho. Biên dịch có điều kiện dựa trên các thuộc tính mà bạn không thể biết khi viết mã (như OS / Compiler). Sử dụng các cấu trúc ngôn ngữ khác.
Martin York

Câu trả lời:


67

A typedefthường được ưa thích trừ khi có một số lý do kỳ lạ mà bạn đặc biệt cần một macro.

các macro thực hiện thay thế văn bản, có thể gây ra bạo lực đáng kể đối với ngữ nghĩa của mã. Ví dụ: đã cho:

#define MYINTEGER int

bạn có thể viết một cách hợp pháp:

short MYINTEGER x = 42;

short MYINTEGERmở rộng để short int.

Mặt khác, với một typedef:

typedef int MYINTEGER:

tên MYINTEGERlà một tên khác cho loại int , không phải là một thay thế văn bản cho từ khóa "int".

Mọi thứ thậm chí còn tồi tệ hơn với các loại phức tạp hơn. Ví dụ, đưa ra điều này:

typedef char *char_ptr;
char_ptr a, b;
#define CHAR_PTR char*
CHAR_PTR c, d;

a, bclà tất cả các con trỏ, nhưng dlà một char, bởi vì dòng cuối cùng mở rộng thành:

char* c, d;

tương đương với

char *c;
char d;

(Typedefs cho các loại con trỏ thường không phải là một ý tưởng tốt, nhưng điều này minh họa điểm này.)

Một trường hợp kỳ lạ khác:

#define DWORD long
DWORD double x;     /* Huh? */

1
Con trỏ lại đổ lỗi! :) Bất kỳ ví dụ nào khác ngoài các con trỏ trong đó sử dụng các macro như vậy là không khôn ngoan?
c0da

2
Không phải là tôi đã từng sử dụng một macro thay cho a typedef, nhưng cách thích hợp để xây dựng một macro làm bất cứ điều gì với bất cứ điều gì sau đây là sử dụng các đối số : #define CHAR_PTR(x) char *x. Điều này ít nhất sẽ khiến trình biên dịch kvetch nếu sử dụng không đúng.
Blrfl

1
Đừng quên:const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
Jon Purdy

3
Định nghĩa cũng khiến thông báo lỗi không thể hiểu được
Thomas Bonini

Một ví dụ về nỗi đau mà việc lạm dụng macro có thể gây ra (mặc dù không liên quan trực tiếp đến macro so với typedefs): github.com/Keith-S-Thndry/42
Keith Thompson

15

Cho đến nay, vấn đề lớn nhất với các macro là chúng không nằm trong phạm vi. Điều đó một mình đảm bảo việc sử dụng typedef. Ngoài ra, nó là rõ ràng hơn về ngữ nghĩa. Khi ai đó đọc mã của bạn nhìn thấy một định nghĩa, anh ta sẽ không biết nó là gì cho đến khi anh ta đọc nó và hiểu toàn bộ macro. Một typedef nói với người đọc rằng một tên loại sẽ được xác định. (Tôi nên đề cập rằng tôi đang nói về C ++. Tôi không chắc chắn về phạm vi typedef cho C, nhưng tôi đoán nó tương tự).


Nhưng như tôi đã chỉ ra trong ví dụ được cung cấp trong câu hỏi, một typedef và #define sử dụng cùng một số từ! Ngoài ra, 3 từ này của macro sẽ không gây nhầm lẫn, vì các từ này giống nhau, nhưng chỉ được sắp xếp lại. :)
c0da

2
Vâng, nhưng nó không phải là về sự ngắn gọn. Một macro có thể làm hàng trăm thứ khác ngoài typedefing. Nhưng cá nhân tôi nghĩ phạm vi là quan trọng nhất.
Tamás Szelei

2
@ c0da - bạn không nên sử dụng macro cho typedefs, bạn nên sử dụng typedefs cho typedefs. Hiệu quả thực tế của macro có thể rất khác nhau, như được minh họa bằng các phản ứng khác nhau.
Joris Timmermans

15

Mã nguồn chủ yếu được viết cho các nhà phát triển đồng nghiệp. Máy tính sử dụng một phiên bản biên dịch của nó.

Trong viễn cảnh này typedefmang một ý nghĩa mà #definekhông.


6

Câu trả lời của Keith Thompson rất hay và cùng với các điểm bổ sung của Tamás Szelei về phạm vi sẽ cung cấp tất cả nền tảng bạn cần.

Bạn nên luôn luôn coi macro là phương sách cuối cùng của người tuyệt vọng. Có một số điều bạn chỉ có thể làm với macro. Ngay cả khi đó, bạn nên suy nghĩ lâu dài và chăm chỉ nếu bạn thực sự muốn làm điều đó. Số lượng đau đớn trong việc gỡ lỗi có thể gây ra bởi các macro bị hỏng một cách tinh vi là đáng kể và sẽ giúp bạn xem qua các tệp được xử lý trước. Thật đáng để làm điều đó chỉ một lần, để cảm nhận phạm vi của vấn đề trong C ++ - chỉ kích thước của tệp được xử lý trước có thể là một cái mở mắt.


5

Ngoài tất cả các nhận xét hợp lệ về phạm vi và thay thế văn bản đã được đưa ra, #define cũng không hoàn toàn tương thích với typedef! Cụ thể là khi nói đến trường hợp con trỏ hàm.

typedef void(*fptr_t)(void);

Điều này khai báo một loại fptr_tlà một con trỏ đến một chức năng của loại void func(void).

Bạn không thể khai báo loại này với một macro. #define fptr_t void(*)(void)rõ ràng sẽ không hoạt động. Bạn sẽ phải viết một cái gì đó tối nghĩa như thế #define fptr_t(name) void(*name)(void), điều này thực sự không có ý nghĩa gì trong ngôn ngữ C, nơi chúng tôi không có nhà xây dựng.

Con trỏ mảng không thể được khai báo bằng #define: typedef int(*arr_ptr)[10];


Và trong khi C không có hỗ trợ ngôn ngữ cho loại an toàn đáng được đề cập, một trường hợp khác mà typedef và #define không tương thích là khi bạn thực hiện chuyển đổi loại đáng ngờ. Trình biên dịch và / hoặc công cụ phân tích astatic có thể đưa ra cảnh báo cho các chuyển đổi đó nếu bạn sử dụng typedef.


1
Thậm chí tốt hơn là sự kết hợp của các con trỏ hàm và mảng, như char *(*(*foo())[])();( foolà một hàm trả về một con trỏ tới một mảng các con trỏ tới các hàm trả về con trỏ tới char).
John Bode

4

Sử dụng công cụ có ít năng lượng nhất để hoàn thành công việc và công cụ có nhiều cảnh báo nhất. #define được đánh giá trong bộ tiền xử lý, bạn chủ yếu dựa vào đó. typedef được đánh giá bởi trình biên dịch. Kiểm tra được đưa ra và typedef chỉ có thể xác định các loại, như tên nói. Vì vậy, trong ví dụ của bạn chắc chắn đi cho typedef.


2

Ngoài các đối số bình thường chống lại định nghĩa, bạn sẽ viết hàm này bằng cách sử dụng Macros như thế nào?

template <typename IterType>
typename IterType::value_type Sum(
    const IterType& begin, 
    const IterType& end, 
    const IterType::value_type& initialValue)
{
    typename IterType::value_type result = initialValue;
    for (IterType i = begin; i != end; ++i)
        result += i;

    return result;
}

....

vector<int> values;
int sum = Sum(values.begin(), values.end(), 0);

Đây rõ ràng là một ví dụ tầm thường, nhưng hàm đó có thể tổng hợp trên bất kỳ chuỗi lặp lại chuyển tiếp nào của loại thực hiện phép cộng *. Typedefs được sử dụng như thế này là một yếu tố quan trọng của Lập trình chung .

* Tôi vừa viết bài này ở đây, tôi để lại biên dịch nó như một bài tập cho người đọc :-)

CHỈNH SỬA:

Câu trả lời này có vẻ là nguyên nhân rất nhiều nhầm lẫn, vì vậy hãy để tôi rút ra nhiều hơn. Nếu bạn nhìn vào bên trong định nghĩa của vectơ STL, bạn sẽ thấy một cái gì đó tương tự như sau:

template <typename ValueType, typename AllocatorType>
class vector
{
public:
    typedef ValueType value_type;
...
}

Việc sử dụng typedefs trong các thùng chứa tiêu chuẩn cho phép một hàm chung (như hàm tôi đã tạo ở trên) để tham chiếu các loại này. Hàm "Sum" được tạo mẫu trên loại container ( std::vector<int>), không phải trên loại được giữ bên trong container ( int). Nếu không có typedef thì không thể tham khảo kiểu nội bộ đó.

Do đó, typedefs là trung tâm của Modern C ++ và điều này là không thể với Macros.


Câu hỏi được hỏi về macro vs typedef, không phải macro so với các hàm nội tuyến.
Ben Voigt

Chắc chắn, và điều này chỉ có thể với typedefs ... đó là giá trị là gì.
Chris Pitman

Đây không phải là một typedef, nó là lập trình mẫu. Hàm hoặc functor không phải là một kiểu, bất kể nó chung chung như thế nào.

@Lundin Tôi đã thêm chi tiết: Hàm Sum chỉ có thể được vì các thùng chứa tiêu chuẩn sử dụng typedefs để hiển thị các loại mà chúng được tạo khuôn. Tôi không nói rằng Sum là một typedef. Tôi đang nói std :: vector <T> :: value_type là một typedef. Vui lòng xem nguồn gốc của bất kỳ triển khai thư viện tiêu chuẩn nào để xác minh.
Chris Pitman

Đủ công bằng. Có lẽ sẽ tốt hơn nếu chỉ đăng đoạn trích thứ hai.

1

typedefphù hợp với triết lý C ++: tất cả các kiểm tra / khẳng định có thể có trong thời gian biên dịch. #definechỉ là một thủ thuật tiền xử lý ẩn rất nhiều ngữ nghĩa đối với trình biên dịch. Bạn không nên lo lắng về hiệu suất biên dịch nhiều hơn tính chính xác của mã.

Khi bạn tạo một loại mới, bạn đang xác định một "thứ" mới mà miền của chương trình của bạn thao tác. Vì vậy, bạn có thể sử dụng "thứ" này để soạn các hàm và các lớp và bạn có thể sử dụng trình biên dịch vì lợi ích của bạn để thực hiện kiểm tra tĩnh. Dù sao, vì C ++ tương thích với C, có rất nhiều chuyển đổi ngầm giữa intvà các loại dựa trên int không tạo ra cảnh báo. Vì vậy, bạn không có được toàn bộ sức mạnh của kiểm tra tĩnh trong trường hợp này. Tuy nhiên, bạn có thể thay thế typedefbằng một enumđể tìm chuyển đổi ngầm định. Ví dụ: Nếu là bạn typedef int Age;, thì bạn có thể thay thế bằng enum Age { };và bạn sẽ nhận được tất cả các loại lỗi bằng cách chuyển đổi ngầm giữa Ageint.

Một điều nữa: typedefcó thể ở bên trong a namespace.


1

Sử dụng định nghĩa thay vì typedefs gặp rắc rối dưới một khía cạnh quan trọng khác, và cụ thể là khái niệm về đặc điểm loại. Xem xét các lớp khác nhau (nghĩ về các thùng chứa tiêu chuẩn) tất cả đều xác định typedefs cụ thể của chúng. Bạn có thể viết mã chung bằng cách tham khảo các typedefs. Ví dụ về điều này bao gồm các yêu cầu vùng chứa chung (tiêu chuẩn c ++ 23.2.1 [container.requirements.general]) như

X::value_type
X::reference
X::difference_type
X::size_type

Tất cả những điều này không thể diễn tả theo một cách chung chung với một macro vì nó không nằm trong phạm vi.


1

Đừng quên ý nghĩa của việc này trong trình gỡ lỗi của bạn. Một số trình gỡ lỗi không xử lý #define rất tốt. Chơi xung quanh với cả hai trong trình gỡ lỗi bạn sử dụng. Hãy nhớ rằng, bạn sẽ dành nhiều thời gian để đọc nó hơn là viết nó.


-2

"#Define" sẽ thay thế những gì bạn đã viết sau đó, typedef sẽ tạo loại. Vì vậy, nếu bạn muốn có loại tùy chỉnh - sử dụng typedef. Nếu bạn cần macro - sử dụng xác định.


Tôi chỉ yêu những người không bình chọn và thậm chí không buồn viết bình luận.
Dainius
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.