Chuyển tiếp khai báo một enum trong C ++


265

Tôi đang cố gắng làm một cái gì đó như sau:

enum E;

void Foo(E e);

enum E {A, B, C};

mà trình biên dịch từ chối. Tôi đã có một cái nhìn nhanh về Google và sự đồng thuận dường như là "bạn không thể làm được", nhưng tôi không thể hiểu tại sao. Bất cứ ai có thể giải thích?

Làm rõ 2: Tôi đang làm điều này vì tôi có các phương thức riêng tư trong một lớp có enum nói và tôi không muốn các giá trị của enum bị lộ - vì vậy, ví dụ, tôi không muốn ai biết rằng E được định nghĩa là

enum E {
    FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X
}

vì dự án X không phải là điều tôi muốn người dùng của mình biết.

Vì vậy, tôi muốn chuyển tiếp khai báo enum để tôi có thể đặt các phương thức riêng tư vào tệp tiêu đề, khai báo enum bên trong cpp và phân phối tệp thư viện được xây dựng và tiêu đề cho mọi người.

Đối với trình biên dịch - đó là GCC.


Vì vậy, nhiều năm sau đó và bằng cách nào đó, StackOverflow đã thu hút tôi trở lại;) Như một gợi ý sau khi kết thúc - đừng làm điều này đặc biệt là trong kịch bản bạn mô tả. Tôi muốn xác định một giao diện trừu tượng và hiển thị tổng số người dùng này và giữ định nghĩa enum và tất cả các chi tiết triển khai khác với việc triển khai bên trong mà không ai thấy ở bên tôi cho phép tôi làm bất cứ khi nào và có toàn quyền kiểm soát khi người dùng nhìn thấy bất cứ điều gì
RnR

Câu trả lời:


217

Lý do enum không thể được chuyển tiếp khai báo là vì không biết các giá trị, trình biên dịch không thể biết được dung lượng lưu trữ cần thiết cho biến enum. Trình biên dịch C ++ được phép chỉ định không gian lưu trữ thực tế dựa trên kích thước cần thiết để chứa tất cả các giá trị được chỉ định. Nếu tất cả những gì có thể nhìn thấy là khai báo chuyển tiếp, đơn vị dịch thuật không thể biết kích thước lưu trữ nào sẽ được chọn - đó có thể là char hoặc int hoặc thứ gì khác.


Từ Mục 7.2.5 của Tiêu chuẩn ISO C ++:

Các loại cơ bản của một liệt kê là một loại không thể thiếu mà có thể đại diện cho tất cả các giá trị điều tra viên xác định trong kiểu liệt kê. Nó được định nghĩa theo kiểu triển khai được sử dụng làm kiểu tích phân được sử dụng làm kiểu liệt kê cho phép liệt kê ngoại trừ kiểu cơ sở không được lớn hơn inttrừ khi giá trị của một điều tra viên không thể phù hợp với một inthoặc unsigned int. Nếu danh sách liệt kê trống, loại bên dưới như thể liệt kê có một liệt kê duy nhất có giá trị 0. Giá trị sizeof()được áp dụng cho loại liệt kê, đối tượng của kiểu liệt kê hoặc liệt kê, là giá trị sizeof()được áp dụng cho loại cơ bản.

người gọi đến hàm phải biết kích thước của các tham số để thiết lập chính xác ngăn xếp cuộc gọi, nên phải biết số lượng liệt kê trong danh sách liệt kê trước nguyên mẫu hàm.

Cập nhật: Trong C ++ 0X, một cú pháp khai báo trước các kiểu enum đã được đề xuất và chấp nhận. Bạn có thể xem đề xuất tại http://www.open-std.org/jtc1/sc22/wg21/docs/ con 2008 / n2764.pdf


29
-1. Lý luận của bạn không thể đúng - nếu không, tại sao bạn được phép khai báo "lớp C;" và sau đó khai báo một nguyên mẫu hàm lấy hoặc trả về C, trước khi xác định đầy đủ C?
j_random_hacker

112
@j_random: Bạn không thể sử dụng một lớp trước khi nó được xác định đầy đủ - bạn chỉ có thể sử dụng một con trỏ hoặc tham chiếu đến lớp đó và vì kích thước và cách hoạt động của chúng không phụ thuộc vào lớp đó là gì.
RnR

27
Kích thước của một tham chiếu hoặc con trỏ tới một đối tượng lớp được trình biên dịch đặt và không phụ thuộc vào kích thước thực của đối tượng - đó là kích thước của các con trỏ và tham chiếu. Enum là một đối tượng và kích thước của nó là cần thiết để trình biên dịch truy cập vào bộ lưu trữ chính xác.
KJAWolf

16
Về mặt logic, nó có thể khai báo các con trỏ / tham chiếu đến các enum nếu chúng ta có các enum khai báo chuyển tiếp, giống như chúng ta có thể làm với các lớp. Chỉ là bạn không thường xuyên đối phó với con trỏ đến enum :)
Pavel Minaev

20
Tôi biết cuộc thảo luận này đã kết thúc từ lâu, nhưng tôi phải xếp hàng với @j_random_hacker ở đây: vấn đề ở đây không phải là về con trỏ hoặc tham chiếu đến các loại không đầy đủ, mà là về việc sử dụng các loại không đầy đủ trong khai báo. Vì đó là hợp pháp để làm struct S; void foo(S s);(lưu ý rằng foochỉ được khai báo, không được xác định), nên không có lý do gì chúng tôi không thể làm enum E; void foo(E e);như vậy. Trong cả hai trường hợp, kích thước là không cần thiết.
Luc Touraille

200

Chuyển tiếp khai báo enums là có thể kể từ C ++ 11. Trước đây, lý do các loại enum không thể được tuyên bố chuyển tiếp là vì kích thước của bảng liệt kê phụ thuộc vào nội dung của nó. Miễn là kích thước của bảng liệt kê được chỉ định bởi ứng dụng, nó có thể được chuyển tiếp khai báo:

enum Enum1;                   //Illegal in C++ and C++0x; no size is explicitly specified.
enum Enum2 : unsigned int;    //Legal in C++0x.
enum class Enum3;             //Legal in C++0x, because enum class declarations have a default type of "int".
enum class Enum4: unsigned int; //Legal C++0x.
enum Enum2 : unsigned short;  //Illegal in C++0x, because Enum2 was previously declared with a different type.

1
Có bất kỳ trình biên dịch hỗ trợ cho tính năng này? GCC 4.5 dường như không có nó :(
rubenvb

4
@rubenvb Vì vậy, hiện Visual C ++ 11 (2012) blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
knatten

Tôi đang tìm enum32_t và với câu trả lời của bạn enum XXX: uint32_t {a, b, c};
tưởng tượng 17/2/2015

Tôi nghĩ enum phạm vi (lớp enum) đã được thực hiện trong C ++ 11? Nếu vậy, làm thế nào họ hợp pháp trong C ++ 0X?
Terrabits

1
C ++ 0x là tên làm việc của C ++ 11, @Terrabits, trước khi nó được chuẩn hóa chính thức. Logic là nếu một tính năng được biết (hoặc rất có thể) được đưa vào một tiêu chuẩn được cập nhật, thì việc sử dụng tính năng đó trước khi tiêu chuẩn được phát hành chính thức có xu hướng sử dụng tên làm việc. (Vd ngay bây giờ (2019) có hỗ trợ C ++ 2a.)
Justin Time - Tái lập Monica

79

Tôi đang thêm một câu trả lời cập nhật ở đây, với những phát triển gần đây.

Bạn có thể chuyển tiếp khai báo một enum trong C ++ 11, miễn là bạn khai báo loại lưu trữ của nó cùng một lúc. Cú pháp trông như thế này:

enum E : short;
void foo(E e);

....

enum E : short
{
    VALUE_1,
    VALUE_2,
    ....
}

Trong thực tế, nếu hàm không bao giờ đề cập đến các giá trị của phép liệt kê, thì bạn không cần khai báo đầy đủ tại thời điểm đó.

Điều này được hỗ trợ bởi G ++ 4.6 trở đi ( -std=c++0xhoặc -std=c++11trong các phiên bản gần đây hơn). Visual C ++ 2013 hỗ trợ này; trong các phiên bản trước, nó có một số loại hỗ trợ không chuẩn mà tôi chưa tìm ra - tôi thấy một số gợi ý rằng một tuyên bố chuyển tiếp đơn giản là hợp pháp, nhưng YMMV.


4
+1 vì đây là câu trả lời duy nhất đề cập đến bạn cần khai báo loại trong khai báo cũng như định nghĩa của bạn.
Turoni

Tôi tin rằng sự hỗ trợ một phần trong MSVC ban đầu đã được hỗ trợ từ C ++ / CLI enum classdưới dạng phần mở rộng C ++ (trước C ++ 11 khác nhau enum class), ít nhất là nếu tôi nhớ chính xác. Trình biên dịch cho phép bạn chỉ định loại cơ bản của enum, nhưng không hỗ trợ enum classhoặc khai báo chuyển tiếp và cảnh báo bạn rằng việc đánh giá một điều tra viên với phạm vi của enum là một phần mở rộng không chuẩn. Tôi nhớ nó hoạt động gần giống như chỉ định loại cơ bản trong C ++ 11, ngoại trừ khó chịu hơn vì bạn phải loại bỏ cảnh báo.
Thời gian của Justin - Tái lập lại

30

Chuyển tiếp khai báo mọi thứ trong C ++ rất hữu ích vì nó tăng tốc đáng kể thời gian biên dịch . Bạn mong có thể khai báo một vài điều trong C ++ bao gồm: struct, class, function, vv ...

Nhưng bạn có thể chuyển tiếp khai báo enumtrong C ++ không?

Không, bạn không thể.

Nhưng tại sao không cho phép nó? Nếu nó được cho phép, bạn có thể xác định enumloại của mình trong tệp tiêu đề và các enumgiá trị trong tệp nguồn của bạn. Âm thanh như nó nên được cho phép phải không?

Sai lầm.

Trong C ++ không có loại mặc định enumgiống như có trong C # (int). Trong C ++, enumkiểu của bạn sẽ được trình biên dịch xác định là bất kỳ loại nào phù hợp với phạm vi giá trị bạn có cho bạn enum.

Điều đó nghĩa là gì?

Điều đó có nghĩa là enumloại cơ bản của bạn không thể được xác định đầy đủ cho đến khi bạn có tất cả các giá trị enumđược xác định. Những người bạn không thể tách biệt khai báo và định nghĩa của bạn enum. Và do đó bạn không thể chuyển tiếp khai báo enumtrong C ++.

Tiêu chuẩn ISO C ++ S7.2.5:

Kiểu liệt kê cơ bản là một kiểu tích phân có thể biểu thị tất cả các giá trị liệt kê được xác định trong phép liệt kê. Nó được định nghĩa theo kiểu triển khai được sử dụng làm kiểu tích phân được sử dụng làm kiểu liệt kê cho phép liệt kê ngoại trừ kiểu cơ sở không được lớn hơn inttrừ khi giá trị của một điều tra viên không thể phù hợp với một inthoặc unsigned int. Nếu danh sách liệt kê trống, loại bên dưới như thể liệt kê có một liệt kê duy nhất có giá trị 0. Giá trị sizeof()được áp dụng cho loại liệt kê, đối tượng của kiểu liệt kê hoặc liệt kê, là giá trị sizeof()được áp dụng cho loại cơ bản.

Bạn có thể xác định kích thước của kiểu liệt kê trong C ++ bằng cách sử dụng sizeoftoán tử. Kích thước của kiểu liệt kê là kích thước của kiểu cơ bản. Theo cách này, bạn có thể đoán loại trình biên dịch của bạn đang sử dụng cho enum.

Điều gì xảy ra nếu bạn chỉ định loại của bạn enumrõ ràng như thế này:

enum Color : char { Red=0, Green=1, Blue=2};
assert(sizeof Color == 1);

Bạn có thể chuyển tiếp tuyên bố của bạn enum?

Không. Nhưng tại sao không?

Chỉ định loại an enumkhông thực sự là một phần của tiêu chuẩn C ++ hiện tại. Nó là một phần mở rộng VC ++. Nó sẽ là một phần của C ++ 0x.

Nguồn


15
Câu trả lời này bây giờ đã lỗi thời.
Tom

Thời gian làm cho những kẻ ngốc của tất cả chúng ta. Nhận xét của bạn bây giờ đã lỗi thời; Câu trả lời một thập kỷ!
pjcard

14

[Câu trả lời của tôi là sai, nhưng tôi đã để nó ở đây vì các bình luận rất hữu ích].

Chuyển tiếp khai báo enum là không chuẩn, bởi vì con trỏ đến các loại enum khác nhau không được đảm bảo có cùng kích thước. Trình biên dịch có thể cần phải xem định nghĩa để biết con trỏ kích thước nào có thể được sử dụng với loại này.

Trong thực tế, ít nhất là trên tất cả các trình biên dịch phổ biến, con trỏ tới enum là một kích thước phù hợp. Ví dụ, khai báo chuyển tiếp của enums được cung cấp dưới dạng phần mở rộng ngôn ngữ của Visual C ++.


2
-1. Nếu lý luận của bạn là chính xác, lý luận tương tự sẽ ngụ ý rằng các khai báo chuyển tiếp của các loại lớp không thể được sử dụng để tạo con trỏ tới các loại đó - nhưng chúng có thể.
j_random_hacker

6
+1. Lý luận là chính xác. Trường hợp cụ thể là các nền tảng trong đó sizeof (char *)> sizeof (int *). Cả hai có thể là loại cơ bản cho một enum, tùy thuộc vào phạm vi. Các lớp không có các kiểu cơ bản nên sự tương tự là sai.
MSalters

3
@MSalters: Ví dụ: "struct S {int x;};" Bây giờ, sizeof (S *) phải bằng với kích thước của bất kỳ con trỏ-to-struct khác, vì C ++ cho phép một con trỏ như vậy phải được khai báo và sử dụng trước khi định nghĩa của S ...
j_random_hacker

1
@MSalters: ... Trên nền tảng nơi sizeof (char *)> sizeof (int *), sử dụng con trỏ "kích thước đầy đủ" như vậy cho cấu trúc cụ thể này có thể không hiệu quả, nhưng nó đơn giản hóa đáng kể mã hóa - và hoàn toàn giống nhau điều có thể, và nên, được thực hiện cho các loại enum.
j_random_hacker

4
Con trỏ tới dữ liệu và con trỏ tới các hàm có thể có kích thước khác nhau, nhưng tôi khá chắc chắn rằng con trỏ dữ liệu phải đi khứ hồi (chuyển sang loại con trỏ dữ liệu khác, sau đó quay lại bản gốc, vẫn cần hoạt động), ngụ ý rằng tất cả con trỏ dữ liệu có cùng kích thước.
Ben Voigt

7

Thực sự không có thứ gọi là enum phía trước. Vì định nghĩa của enum không chứa bất kỳ mã nào có thể phụ thuộc vào mã khác khi sử dụng enum, nên thường không phải là vấn đề để xác định enum hoàn toàn khi bạn lần đầu tiên khai báo.

Nếu việc sử dụng enum duy nhất của bạn là bởi các hàm thành viên riêng, bạn có thể thực hiện đóng gói bằng cách sử dụng enum như một thành viên riêng của lớp đó. Enum vẫn phải được xác định đầy đủ tại điểm khai báo, nghĩa là trong định nghĩa lớp. Tuy nhiên, đây không phải là một vấn đề lớn hơn khi tuyên bố các chức năng thành viên tư nhân ở đó, và không phải là một triển khai tồi tệ hơn của nội bộ thực hiện hơn thế.

Nếu bạn cần một mức độ che giấu sâu hơn cho các chi tiết triển khai của mình, bạn có thể chia nó thành một giao diện trừu tượng, chỉ bao gồm các hàm ảo thuần túy và một lớp cụ thể, được che giấu hoàn toàn, thực hiện lớp (kế thừa) giao diện. Việc tạo các thể hiện của lớp có thể được xử lý bởi một nhà máy hoặc một chức năng thành viên tĩnh của giao diện. Bằng cách đó, ngay cả tên lớp thực, chưa nói đến các chức năng riêng tư của nó, sẽ không bị lộ.


5

Chỉ cần lưu ý rằng lý do thực sự kích thước của enum vẫn chưa được biết sau khi tuyên bố chuyển tiếp. Chà, bạn sử dụng khai báo về phía trước của một cấu trúc để có thể vượt qua một con trỏ xung quanh hoặc tham chiếu đến một đối tượng từ một nơi được giới thiệu trong chính định nghĩa cấu trúc khai báo phía trước.

Chuyển tiếp tuyên bố một enum sẽ không quá hữu ích, bởi vì người ta muốn có thể vượt qua giá trị enum. Bạn thậm chí không thể có một con trỏ tới nó, bởi vì gần đây tôi đã nói rằng một số nền tảng sử dụng các con trỏ có kích thước khác nhau cho char hơn là int hoặc dài. Vì vậy, tất cả phụ thuộc vào nội dung của enum.

Tiêu chuẩn C ++ hiện tại không cho phép làm điều gì đó như

enum X;

(trong 7.1.5.3/1). Nhưng C ++ chuẩn tới do năm tiếp theo cho phép những điều sau đây, trong đó thuyết phục tôi vấn đề thực sự để làm với các loại cơ bản:

enum X : int;

Nó được gọi là một tuyên bố enum "mờ đục". Bạn thậm chí có thể sử dụng X theo giá trị trong mã sau đây. Và các điều tra viên của nó sau này có thể được định nghĩa trong một bản kê khai sau này của bảng liệt kê. Xem 7.2trong dự thảo làm việc hiện tại.


4

Tôi sẽ làm theo cách này:

[trong tiêu đề công khai]

typedef unsigned long E;

void Foo(E e);

[trong tiêu đề nội bộ]

enum Econtent { FUNCTIONALITY_NORMAL, FUNCTIONALITY_RESTRICTED, FUNCTIONALITY_FOR_PROJECT_X,
  FORCE_32BIT = 0xFFFFFFFF };

Bằng cách thêm FORCE_32BIT, chúng tôi đảm bảo rằng Econtent biên dịch dài, do đó, nó có thể hoán đổi với E.


1
Tất nhiên, điều này có nghĩa là (A) các loại E và Econtent khác nhau và (B) trên các hệ thống LP64, sizeof (E) = 2 * sizeof (EContent). Sửa lỗi tầm thường: ULONG_MAX, cũng dễ đọc hơn.
MSalters

2

Nếu bạn thực sự không muốn enum của mình xuất hiện trong tệp tiêu đề VÀ đảm bảo rằng nó chỉ được sử dụng bởi các phương thức riêng tư, thì một giải pháp có thể là đi theo nguyên tắc pimpl.

Đây là một kỹ thuật đảm bảo ẩn nội bộ lớp trong các tiêu đề bằng cách chỉ cần khai báo:

class A 
{
public:
    ...
private:
    void* pImpl;
};

Sau đó, trong tệp thực hiện của bạn (cpp), bạn khai báo một lớp sẽ là đại diện của các phần bên trong.

class AImpl
{
public:
    AImpl(A* pThis): m_pThis(pThis) {}

    ... all private methods here ...
private:
    A* m_pThis;
};

Bạn phải tự động tạo việc triển khai trong hàm tạo của lớp và xóa nó trong hàm hủy và khi thực hiện phương thức chung, bạn phải sử dụng:

((AImpl*)pImpl)->PrivateMethod();

Có những ưu điểm khi sử dụng pimpl, một là nó tách rời tiêu đề lớp của bạn khỏi việc triển khai nó, không cần phải biên dịch lại các lớp khác khi thay đổi một lớp thực hiện. Một cách khác là tăng tốc thời gian biên dịch của bạn vì các tiêu đề của bạn rất đơn giản.

Nhưng đó là một nỗi đau để sử dụng, vì vậy bạn thực sự nên tự hỏi mình nếu chỉ tuyên bố enum của bạn là riêng tư trong tiêu đề có phải là một vấn đề quá nhiều.


3
cấu trúc AImpl; struct A {private: AImpl * pImpl; };

2

Bạn có thể bọc enum trong một cấu trúc, thêm vào một số hàm tạo và chuyển đổi loại và chuyển tiếp khai báo cấu trúc thay thế.

#define ENUM_CLASS(NAME, TYPE, VALUES...) \
struct NAME { \
    enum e { VALUES }; \
    explicit NAME(TYPE v) : val(v) {} \
    NAME(e v) : val(v) {} \
    operator e() const { return e(val); } \
    private:\
        TYPE val; \
}

Điều này dường như hoạt động: http://ideone.com/TYtP2


1

Có vẻ như nó không thể được tuyên bố trước trong GCC!

Thảo luận thú vị ở đây


1

Có một số bất đồng quan điểm vì điều này đã bị lỗi (loại), vì vậy đây là một số bit có liên quan từ tiêu chuẩn. Nghiên cứu cho thấy rằng tiêu chuẩn không thực sự xác định khai báo chuyển tiếp, cũng không nói rõ rằng enums có thể hoặc không thể được tuyên bố chuyển tiếp.

Đầu tiên, từ dcl.enum, phần 7.2:

Kiểu liệt kê cơ bản là một kiểu tích phân có thể biểu thị tất cả các giá trị liệt kê được xác định trong phép liệt kê. Nó được định nghĩa theo kiểu triển khai được sử dụng làm kiểu tích phân được sử dụng làm kiểu liệt kê cho phép liệt kê ngoại trừ kiểu cơ sở không được lớn hơn int trừ khi giá trị của một điều tra viên không thể khớp với int hoặc unsign int. Nếu danh sách liệt kê trống, loại bên dưới như thể liệt kê có một liệt kê duy nhất có giá trị 0. Giá trị của sizeof () được áp dụng cho loại liệt kê, đối tượng của kiểu liệt kê hoặc liệt kê, là giá trị của sizeof () áp dụng cho kiểu cơ bản.

Vì vậy, kiểu cơ bản của một enum được xác định theo triển khai, với một hạn chế nhỏ.

Tiếp theo, chúng ta lật sang phần "các loại không hoàn chỉnh" (3.9), gần giống với bất kỳ tiêu chuẩn nào về khai báo chuyển tiếp:

Một lớp đã được khai báo nhưng không được xác định hoặc một mảng có kích thước không xác định hoặc loại phần tử không hoàn chỉnh, là một loại đối tượng được xác định không đầy đủ.

Một loại lớp (chẳng hạn như "lớp X") có thể không đầy đủ tại một điểm trong một đơn vị dịch thuật và hoàn thành sau này; loại "lớp X" là cùng loại ở cả hai điểm. Kiểu khai báo của một đối tượng mảng có thể là một mảng của kiểu lớp không hoàn chỉnh và do đó không đầy đủ; nếu loại lớp được hoàn thành sau này trong đơn vị dịch thuật, kiểu mảng sẽ hoàn thành; kiểu mảng tại hai điểm đó là cùng loại. Kiểu khai báo của một đối tượng mảng có thể là một mảng có kích thước không xác định và do đó không đầy đủ tại một điểm trong một đơn vị dịch thuật và hoàn thành sau này; các kiểu mảng tại hai điểm đó ("mảng không xác định ràng buộc của T" và "mảng N T") là các loại khác nhau. Loại con trỏ tới mảng có kích thước không xác định hoặc loại được xác định bởi khai báo typedef là một mảng có kích thước không xác định,

Vì vậy, tiêu chuẩn khá nhiều đặt ra các loại có thể được tuyên bố chuyển tiếp. Enum không có ở đó, vì vậy các tác giả biên dịch thường coi việc khai báo về phía trước là không được phép bởi tiêu chuẩn do kích thước thay đổi của loại cơ bản của nó.

Nó có ý nghĩa, quá. Enums thường được tham chiếu trong các tình huống theo giá trị và trình biên dịch thực sự sẽ cần biết kích thước lưu trữ trong các tình huống đó. Do kích thước lưu trữ được xác định theo triển khai, nhiều trình biên dịch có thể chỉ chọn sử dụng các giá trị 32 bit cho kiểu cơ bản của mọi enum, tại thời điểm đó có thể chuyển tiếp khai báo chúng. Một thử nghiệm thú vị có thể là thử chuyển tiếp khai báo enum trong phòng thu trực quan, sau đó buộc nó sử dụng loại cơ bản lớn hơn sizeof (int) như đã giải thích ở trên để xem điều gì xảy ra.


lưu ý rằng nó rõ ràng không cho phép "enum foo;" trong 7.1.5.3/1 (nhưng cũng như mọi thứ, miễn là trình biên dịch cảnh báo, tất nhiên nó vẫn có thể biên dịch mã như vậy)
Johannes Schaub - litb

Cảm ơn bạn đã chỉ ra, đó là một đoạn thực sự bí truyền và tôi có thể mất một tuần để phân tích nó. Nhưng thật tốt khi biết nó ở đó.
Dan Olson

không phải lo lắng. Một số đoạn tiêu chuẩn thực sự rất lạ :) tốt, một công cụ xác định kiểu phức tạp là thứ mà bạn chỉ định một loại, nhưng cũng chỉ định một cái gì đó nhiều hơn để làm cho nó rõ ràng. ví dụ: "struct X" thay vì "X" hoặc "enum Y" thay vì chỉ "Y". Bạn cần nó để khẳng định một cái gì đó thực sự là một loại.
Julian Schaub - litb

vì vậy bạn có thể sử dụng nó như thế này: "class X * foo;" nếu X chưa được tuyên bố trước. hoặc "typename X :: foo" trong một mẫu để định hướng. hoặc "liên kết lớp obj;" nếu có một hàm "liên kết" trong cùng một phạm vi sẽ phủ bóng lên lớp có cùng tên.
Julian Schaub - litb

trong 3.4.4, nó cho biết chúng được sử dụng nếu một số tên không phải loại ẩn tên loại. đó là nơi chúng thường được sử dụng nhất, ngoài việc khai báo về phía trước như "lớp X;" (ở đây nó là cấu thành duy nhất của một tuyên bố). nó nói về họ trong các mẫu phi ở đây. tuy nhiên, 14.6 / 3 liệt kê việc sử dụng chúng trong các mẫu.
Julian Schaub - litb

1

Đối với VC, đây là bài kiểm tra về khai báo chuyển tiếp và chỉ định loại cơ bản:

  1. đoạn mã sau được biên dịch ok.
    typedef int myint;
    enum T;
    void foo (T * tp)
    {
        * tp = (T) 0x12345678;
    }
    enum T: char
    {
        Một
    };

Nhưng nhận được cảnh báo cho / W4 (/ W3 không phát sinh cảnh báo này)

cảnh báo C4480: tiện ích mở rộng không chuẩn được sử dụng: chỉ định loại cơ bản cho enum 'T'

  1. VC (Microsoft (R) Trình biên dịch tối ưu hóa C / C ++ 32 bit Phiên bản 15.00.30729.01 cho 80x86) có vẻ có lỗi trong trường hợp trên:

    • khi thấy enum T; VC giả sử kiểu enum T sử dụng int 4 byte mặc định làm kiểu cơ bản, vì vậy mã lắp ráp được tạo là:
    ? foo @@ YAXPAW4T @@@ Z PROC; foo
    ; Tệp e: \ work \ c_cpp \ cpp_snippet.cpp
    ; Dòng 13
        đẩy ebp
        Mov ebp, đặc biệt
    ; Dòng 14
        Mov eax, DWORD PTR _tp $ [ebp]
        Mov DWORD PTR [eax], 305419896; 12345678H
    ; Dòng 15
        pop ebp
        giữ lại 0
    ? foo @@ YAXPAW4T @@@ Z ENDP; foo

Mã lắp ráp ở trên được trích xuất trực tiếp từ /Fatest.asm, không phải là phỏng đoán cá nhân của tôi. Bạn có thấy Mov DWORD PTR [eax], 305419896; Dòng 12345678H?

đoạn mã sau chứng minh điều đó:

    int chính (int argc, char * argv)
    {
        liên hiệp {
            char ca [4];
            T t;
        } a;
        a.ca [0] = a.ca [1] = a. [ca [2] = a.ca [3] = 1;
        foo (& a.t);
        printf ("% # x,% # x,% # x,% # x \ n", a.ca [0], a.ca [1], a.ca [2], a.ca [3]) ;
        trả về 0;
    }

kết quả là: 0x78, 0x56, 0x34, 0x12

  • sau khi xóa khai báo chuyển tiếp của enum T và di chuyển định nghĩa của hàm foo sau định nghĩa của enum T: kết quả là OK:

hướng dẫn chính ở trên trở thành:

Mov BYTE PTR [eax], 120; 00000078H

kết quả cuối cùng là: 0x78, 0x1, 0x1, 0x1

Lưu ý rằng giá trị không bị ghi đè

Vì vậy, việc sử dụng khai báo chuyển tiếp của enum trong VC được coi là có hại.

BTW, để không ngạc nhiên, cú pháp khai báo kiểu cơ bản giống như trong C #. Trong pratice tôi thấy rằng đáng để lưu 3 byte bằng cách chỉ định loại cơ bản là char khi nói chuyện với hệ thống nhúng, bị giới hạn bộ nhớ.


1

Trong các dự án của mình, tôi đã áp dụng kỹ thuật liệt kê Namespace-Bound để xử lý enums từ các thành phần kế thừa và bên thứ ba. Đây là một ví dụ:

chuyển tiếp.h:

namespace type
{
    class legacy_type;
    typedef const legacy_type& type;
}

enum.h:

// May be defined here or pulled in via #include.
namespace legacy
{
    enum evil { x , y, z };
}


namespace type
{
    using legacy::evil;

    class legacy_type
    {
    public:
        legacy_type(evil e)
            : e_(e)
        {}

        operator evil() const
        {
            return e_;
        }

    private:
        evil e_;
    };
}

foo.h:

#include "forward.h"

class foo
{
public:
    void f(type::type t);
};

foo.cc:

#include "foo.h"

#include <iostream>
#include "enum.h"

void foo::f(type::type t)
{
    switch (t)
    {
        case legacy::x:
            std::cout << "x" << std::endl;
            break;
        case legacy::y:
            std::cout << "y" << std::endl;
            break;
        case legacy::z:
            std::cout << "z" << std::endl;
            break;
        default:
            std::cout << "default" << std::endl;
    }
}

chính.cc:

#include "foo.h"
#include "enum.h"

int main()
{
    foo fu;
    fu.f(legacy::x);

    return 0;
}

Lưu ý rằng foo.htiêu đề không phải biết bất cứ điều gì về legacy::evil. Chỉ các tệp sử dụng loại kế thừa legacy::evil(ở đây: main.cc) cần bao gồm enum.h.


0

Giải pháp của tôi cho vấn đề của bạn sẽ là:

1 - sử dụng int thay vì enums: Khai báo ints của bạn trong một không gian tên ẩn danh trong tệp CPP của bạn (không phải trong tiêu đề):

namespace
{
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
}

Vì phương thức của bạn là riêng tư, không ai sẽ gây rối với dữ liệu. Bạn thậm chí có thể đi xa hơn để kiểm tra nếu ai đó gửi cho bạn dữ liệu không hợp lệ:

namespace
{
   const int FUNCTIONALITY_begin = 0 ;
   const int FUNCTIONALITY_NORMAL = 0 ;
   const int FUNCTIONALITY_RESTRICTED = 1 ;
   const int FUNCTIONALITY_FOR_PROJECT_X = 2 ;
   const int FUNCTIONALITY_end = 3 ;

   bool isFunctionalityCorrect(int i)
   {
      return (i >= FUNCTIONALITY_begin) && (i < FUNCTIONALITY_end) ;
   }
}

2: tạo một lớp đầy đủ với các giới hạn const giới hạn, giống như được thực hiện trong Java. Chuyển tiếp khai báo lớp, và sau đó định nghĩa nó trong tệp CPP và chỉ cung cấp các giá trị giống như enum. Tôi đã làm một cái gì đó tương tự trong C ++ và kết quả không được thỏa mãn như mong muốn, vì nó cần một số mã để mô phỏng một enum (bản sao xây dựng, toán tử =, v.v.).

3: Như đề xuất trước đây, sử dụng enum khai báo riêng. Mặc dù thực tế người dùng sẽ thấy định nghĩa đầy đủ của nó, nhưng nó sẽ không thể sử dụng nó, cũng không sử dụng các phương thức riêng tư. Vì vậy, thông thường bạn sẽ có thể sửa đổi enum và nội dung của các phương thức hiện có mà không cần biên dịch lại mã bằng lớp của bạn.

Tôi đoán sẽ là giải pháp 3 hoặc 1.


-1

Vì enum có thể là một kích thước tích phân có kích thước khác nhau (trình biên dịch quyết định kích thước của một enum đã cho), con trỏ tới enum cũng có thể có kích thước khác nhau, vì nó là một loại tách rời (chars có con trỏ có kích thước khác nhau trên một số nền tảng ví dụ).

Vì vậy, trình biên dịch thậm chí không thể cho phép bạn chuyển tiếp khai báo enum và người dùng một con trỏ tới nó, bởi vì ngay cả ở đó, nó cần kích thước của enum.


-1

Bạn xác định một phép liệt kê để hạn chế các giá trị có thể có của các phần tử của loại thành một tập hợp giới hạn. Hạn chế này sẽ được thi hành tại thời điểm biên dịch.

Khi chuyển tiếp tuyên bố thực tế là bạn sẽ sử dụng 'bộ giới hạn' sau này không thêm bất kỳ giá trị nào: mã tiếp theo cần biết các giá trị có thể để hưởng lợi từ nó.

Mặc dù trình biên dịch lo ngại về kích thước của kiểu liệt kê, các ý định thuộc kiểu liệt kê bị mất khi bạn về phía trước khai báo nó.


1
Không, mã tiếp theo không cần biết các giá trị này là hữu ích - đặc biệt, nếu mã tiếp theo chỉ là một nguyên mẫu hàm lấy hoặc trả về các tham số enum, kích thước của loại không quan trọng. Sử dụng khai báo chuyển tiếp ở đây có thể loại bỏ các phụ thuộc xây dựng, tăng tốc biên dịch.
j_random_hacker

Bạn đúng. Mục đích không phải là tuân theo các giá trị, mà là loại. Được giải quyết với các loại Enx 0x.
xtofl
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.