Tôi nên sử dụng #define, enum hay const?


125

Trong một dự án C ++ tôi đang làm việc, tôi có một loại giá trị cờ có thể có bốn giá trị. Bốn lá cờ có thể được kết hợp. Cờ mô tả các hồ sơ trong cơ sở dữ liệu và có thể là:

  • kỷ lục mới
  • xóa hồ sơ
  • sửa đổi hồ sơ
  • hồ sơ hiện có

Bây giờ, với mỗi bản ghi tôi muốn giữ thuộc tính này, vì vậy tôi có thể sử dụng một enum:

enum { xNew, xDeleted, xModified, xExisting }

Tuy nhiên, ở những nơi khác trong mã, tôi cần chọn bản ghi nào sẽ hiển thị cho người dùng, vì vậy tôi muốn có thể chuyển thông tin đó dưới dạng một tham số, như:

showRecords(xNew | xDeleted);

Vì vậy, có vẻ như tôi có ba appoaches có thể:

#define X_NEW      0x01
#define X_DELETED  0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08

hoặc là

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

hoặc là

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

Yêu cầu không gian là quan trọng (byte vs int) nhưng không quan trọng. Với định nghĩa tôi mất an toàn kiểu và với enumtôi mất một số không gian (số nguyên) và có lẽ phải bỏ khi tôi muốn thực hiện thao tác bitwise. Với consttôi nghĩ rằng tôi cũng mất an toàn kiểu vì một sự ngẫu nhiên uint8có thể xảy ra do nhầm lẫn.

Có cách nào khác sạch hơn không?

Nếu không, bạn sẽ sử dụng cái gì và tại sao?

PS Phần còn lại của mã khá sạch C ++ hiện đại không có #defines và tôi đã sử dụng các không gian tên và mẫu trong một số khoảng trắng, vì vậy chúng cũng không có vấn đề gì.


"với enum tôi mất một số không gian (số nguyên)". Không cần thiết. Xem stackoverflow.com/questions/366017/ trênstackoverflow.com/questions/1113855/ triệt (và gcc's -fshort-enum . (Tôi giả sử những câu trả lời C này vẫn đúng trong C ++.)
idbrii

@pydave Trong trường hợp bạn không chắc về khả năng tương thích của C và C ++, tôi thấy liên kết này rất hữu ích, xem ví dụ enum david.tribble.com/text/cdiffs.htmlm#C99-enum-type
aka.nice

3
Đây là một chủ đề cũ hơn với số phiếu cao, có lý do để không đề cập đến các lớp enum C ++ 11 cho tình huống vấn đề này.
Brandin

Lưu ý, enum RecordType : uint8_tkết hợp loại an toàn enumvới kích thước nhỏ uint8_t, mặc dù bạn vẫn sẽ cần cung cấp các toán tử bitwise.
Thời gian của Justin - Phục hồi lại

Câu trả lời:


88

Kết hợp các chiến lược để giảm những nhược điểm của một phương pháp duy nhất. Tôi làm việc trong các hệ thống nhúng nên giải pháp sau dựa trên thực tế là các toán tử số nguyên và bitwise nhanh, bộ nhớ thấp và sử dụng flash thấp.

Đặt enum trong một không gian tên để ngăn các hằng số gây ô nhiễm không gian tên toàn cầu.

namespace RecordType {

Một enum tuyên bố và xác định thời gian biên dịch được kiểm tra đánh máy. Luôn sử dụng kiểm tra kiểu thời gian biên dịch để đảm bảo các đối số và biến được đưa ra đúng loại. Không cần typedef trong C ++.

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

Tạo một thành viên khác cho một trạng thái không hợp lệ. Điều này có thể hữu ích như mã lỗi; ví dụ: khi bạn muốn trả về trạng thái nhưng thao tác I / O không thành công. Nó cũng hữu ích để gỡ lỗi; sử dụng nó trong danh sách khởi tạo và hàm hủy để biết có nên sử dụng giá trị của biến không.

xInvalid = 16 };

Hãy xem xét rằng bạn có hai mục đích cho loại này. Để theo dõi trạng thái hiện tại của một bản ghi và tạo mặt nạ để chọn các bản ghi ở các trạng thái nhất định. Tạo một hàm nội tuyến để kiểm tra xem giá trị của loại có hợp lệ cho mục đích của bạn không; như một điểm đánh dấu trạng thái so với mặt nạ trạng thái. Điều này sẽ bắt lỗi vì typedefnó chỉ là một intvà một giá trị như 0xDEADBEEFcó thể nằm trong biến của bạn thông qua các biến chưa được xác định hoặc bị xác định sai.

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

Thêm một lệnh usingnếu bạn muốn sử dụng loại thường xuyên.

using RecordType ::TRecordType ;

Các chức năng kiểm tra giá trị rất hữu ích trong các xác nhận để bẫy các giá trị xấu ngay khi chúng được sử dụng. Bạn càng nhanh chóng bắt được một lỗi khi chạy, nó càng ít gây ra thiệt hại.

Dưới đây là một số ví dụ để đặt tất cả lại với nhau.

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

Cách duy nhất để đảm bảo an toàn giá trị chính xác là sử dụng một lớp chuyên dụng với tình trạng quá tải toán tử và đó là một bài tập cho người đọc khác.


1
Chủ yếu là một câu trả lời hay - nhưng câu hỏi quy định rằng các cờ có thể được kết hợp và hàm IsValidState () không cho phép chúng được kết hợp.
Jonathan Leffler

3
@Jonathan Leffler: từ chỗ tôi đứng, tôi nghĩ rằng 'IsValidState' không được phép làm điều đó, 'IsValidMask' là.
João Portela

1
Có mong muốn IsValidMaskkhông cho phép chọn không (tức là 0)?
Joachim Sauer

2
−1 Ý tưởng kiểm tra kiểu thời gian chạy là một sự gớm ghiếc.
Chúc mừng và hth. - Alf

54

Quên định nghĩa

Họ sẽ làm ô nhiễm mã của bạn.

bitfield?

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

Đừng bao giờ sử dụng nó . Bạn quan tâm đến tốc độ hơn là tiết kiệm 4 ints. Sử dụng các trường bit thực sự chậm hơn so với truy cập vào bất kỳ loại nào khác.

Tuy nhiên, các thành viên bit trong structs có nhược điểm thực tế. Đầu tiên, thứ tự các bit trong bộ nhớ thay đổi từ trình biên dịch sang trình biên dịch. Ngoài ra, nhiều trình biên dịch phổ biến tạo mã không hiệu quả để đọc và ghi các thành viên bit , và có các vấn đề an toàn luồng nghiêm trọng liên quan đến các trường bit (đặc biệt là trên các hệ thống đa bộ xử lý) do hầu hết các máy không thể điều khiển các bộ bit tùy ý trong bộ nhớ, nhưng thay vào đó phải tải và lưu trữ toàn bộ từ. ví dụ như sau đây sẽ không an toàn cho chuỗi, mặc dù sử dụng mutex

Nguồn: http://en.wikipedia.org/wiki/Bit_field :

Và nếu bạn cần thêm lý do để không sử dụng bitfield, có lẽ Raymond Chen sẽ thuyết phục bạn trong The Old New Thing Post: Phân tích lợi ích chi phí của bitfield cho một bộ sưu tập booleans tại http://bloss.msdn.com/oldnewthing/ lưu trữ / 2008/11/26/9143050.aspx

const int?

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

Đặt chúng trong một không gian tên là mát mẻ. Nếu chúng được khai báo trong CPP hoặc tệp tiêu đề của bạn, giá trị của chúng sẽ được nội tuyến. Bạn sẽ có thể sử dụng chuyển đổi trên các giá trị đó, nhưng nó sẽ tăng nhẹ khớp nối.

À, vâng: xóa từ khóa tĩnh . static không được dùng trong C ++ khi bạn sử dụng và nếu uint8 là loại dựng, bạn sẽ không cần điều này để khai báo điều này trong một tiêu đề được bao gồm bởi nhiều nguồn của cùng một mô-đun. Cuối cùng, mã phải là:

namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

Vấn đề của phương pháp này là mã của bạn biết giá trị của các hằng số của bạn, làm tăng nhẹ khớp nối.

enum

Giống như const int, với một kiểu gõ mạnh hơn.

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Họ vẫn đang gây ô nhiễm không gian tên toàn cầu, mặc dù. Bằng cách này ... Loại bỏ typedef . Bạn đang làm việc trong C ++. Những typedefs của enums và structs đang làm ô nhiễm mã hơn bất cứ thứ gì khác.

Kết quả thật tuyệt vời:

enum RecordType { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

Như bạn thấy, enum của bạn đang gây ô nhiễm không gian tên toàn cầu. Nếu bạn đặt enum này trong một không gian tên, bạn sẽ có một cái gì đó như:

namespace RecordType {
   enum Value { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;
}

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

bên ngoài const int?

Nếu bạn muốn giảm khớp nối (nghĩa là có thể ẩn các giá trị của hằng số, và vì vậy, hãy sửa đổi chúng theo ý muốn mà không cần biên dịch lại đầy đủ), bạn có thể khai báo ints là extern trong tiêu đề và là hằng số trong tệp CPP , như trong ví dụ sau:

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

Và:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

Tuy nhiên, bạn sẽ không thể sử dụng chuyển đổi trên các hằng số đó. Vì vậy, cuối cùng, chọn chất độc của bạn ... :-p


5
Tại sao bạn nghĩ bitfield chậm? Bạn đã thực sự định hình mã bằng cách sử dụng nó và phương pháp khác? Ngay cả khi nó là, sự rõ ràng có thể quan trọng hơn tốc độ, làm cho "không bao giờ sử dụng" một chút đơn giản.
wnoise

"const const uint8 xNew;" chỉ dư thừa vì trong C ++ const các biến trong phạm vi không gian tên mặc định cho liên kết bên trong. Hủy bỏ "const" và nó có liên kết bên ngoài. Ngoài ra, "enum {...} RecordType;" khai báo một biến toàn cục có tên "RecordType" có loại là enum ẩn danh.
bk1e

onitherone: Đầu tiên, lý do chính là mức tăng (một vài byte, nếu có) bị lu mờ bởi sự mất mát (truy cập chậm hơn, cả đọc và viết) ...
paercebal

3
onitherone: Thứ hai, Tất cả các mã tôi sản xuất tại nơi làm việc hoặc ở nhà vốn là chủ đề an toàn. Thật dễ dàng để làm: Không toàn cầu, không tĩnh, không chia sẻ giữa các luồng trừ khi được bảo vệ khóa. Sử dụng thành ngữ này sẽ phá vỡ sự an toàn của luồng cơ bản này. Và để làm gì? Một vài byte có lẽ ? ... :-) ...
paercebal

Đã thêm tài liệu tham khảo cho bài viết của Raymond Chen về chi phí ẩn của bitfield.
paercebal

30

Bạn đã loại trừ std :: bitset? Bộ cờ là những gì nó dành cho. Làm

typedef std::bitset<4> RecordType;

sau đó

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

Vì có một loạt các toán tử quá tải cho bitet, bây giờ bạn có thể làm

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

Hoặc một cái gì đó rất giống với điều đó - Tôi đánh giá cao bất kỳ sự điều chỉnh nào vì tôi đã không kiểm tra điều này. Bạn cũng có thể tham khảo các bit theo chỉ mục, nhưng nói chung, tốt nhất là chỉ xác định một bộ hằng và hằng số RecordType có thể hữu ích hơn.

Giả sử bạn đã loại trừ bitet, tôi bỏ phiếu cho enum .

Tôi không mua rằng việc đúc enum là một bất lợi nghiêm trọng - OK vì vậy nó hơi ồn ào và việc gán giá trị ngoài phạm vi cho một enum là hành vi không xác định nên về mặt lý thuyết có thể tự bắn vào chân mình trên một số C ++ bất thường thực hiện. Nhưng nếu bạn chỉ làm điều đó khi cần thiết (đó là khi đi từ int đến enum iirc), thì đó là mã hoàn toàn bình thường mà mọi người đã thấy trước đây.

Tôi cũng nghi ngờ về bất kỳ chi phí không gian nào của enum. Các biến và tham số uint8 có thể sẽ không sử dụng bất kỳ ngăn xếp nào ít hơn ints, vì vậy chỉ lưu trữ trong các lớp. Có một số trường hợp trong đó việc đóng gói nhiều byte trong một cấu trúc sẽ giành chiến thắng (trong trường hợp đó bạn có thể truyền enum vào và ra khỏi bộ lưu trữ uint8), nhưng thông thường, việc đệm sẽ giết chết lợi ích.

Vì vậy, enum không có nhược điểm so với các loại khác, và như một lợi thế mang lại cho bạn một chút an toàn về loại (bạn không thể gán một số giá trị số nguyên ngẫu nhiên mà không cần truyền rõ ràng) và các cách đề cập rõ ràng về mọi thứ.

Đối với sở thích, tôi cũng đặt "= 2" vào enum. Điều đó là không cần thiết, nhưng một "nguyên tắc ít ngạc nhiên nhất" cho thấy rằng cả 4 định nghĩa sẽ giống nhau.


1
Trên thực tế, tôi đã không xem xét bitet nào cả. Tuy nhiên, tôi không chắc nó sẽ tốt. Với bitet, tôi phải giải quyết các bit là 1, 2, 3, 4 để làm cho mã ít đọc hơn - có nghĩa là tôi có thể sẽ sử dụng một enum để 'đặt tên' cho các bit. Có thể là một tiết kiệm không gian mặc dù. Cảm ơn.
Milan Babuškov

Milan, bạn không phải "đặt tên" cho các bit bằng enum, bạn chỉ có thể sử dụng các bit được xác định trước như được hiển thị ở trên. Nếu bạn muốn bật bit một, thay vì my_bitset.flip (1), bạn sẽ thực hiện my_bitset | = xNew;
moswald

điều này ít nhắm vào bạn và nhiều hơn ở STL, nhưng: tôi thực sự phải hỏi: tại sao bạn lại sử dụng bitsetnó? nó thường chuyển thành một long(trong iirc thực hiện của tôi; vâng, thật lãng phí) hoặc loại tích phân tương tự cho mỗi phần tử, vậy tại sao không sử dụng các tích phân không được kích hoạt? (hoặc, ngày nay, constexprvới dung lượng lưu trữ bằng 0)
underscore_d

[sửa thời gian chờ] ... nhưng sau đó tôi chưa bao giờ thực sự hiểu lý do căn bản của bitsetlớp, ngoài những gì dường như là dòng chảy lặp đi lặp lại trong các cuộc thảo luận xung quanh về 'ugh, chúng ta phải che đậy nguồn gốc ngôn ngữ cấp thấp không đáng tin của ngôn ngữ '
gạch dưới

"Các uint8biến và tham số có thể sẽ không sử dụng bất kỳ ngăn xếp nào ít hơn ints" là sai. Nếu bạn có CPU có các thanh ghi 8 bit, intcần ít nhất 2 thanh ghi trong khi uint8_tchỉ cần 1, vì vậy bạn sẽ cần nhiều không gian ngăn xếp hơn vì bạn có nhiều khả năng thoát khỏi các thanh ghi (cũng chậm hơn và có thể tăng kích thước mã ( tùy theo tập lệnh)). (Bạn có một loại, nó phải là uint8_tkhông uint8)
12431234123412341234123


5

Nếu có thể KHÔNG sử dụng macro. Họ không quá ngưỡng mộ khi nói đến C ++ hiện đại.


4
Thật. Điều tôi ghét về macro là bản thân bạn không thể bước vào chúng nếu chúng sai.
Carl

Tôi tưởng tượng đó là một cái gì đó có thể được sửa trong trình biên dịch.
celticminstrel

4

Enums sẽ phù hợp hơn vì chúng cung cấp "ý nghĩa cho các định danh" cũng như an toàn loại. Bạn có thể nói rõ ràng "xDelatted" là "RecordType" và đại diện cho "loại bản ghi" (wow!) Ngay cả sau nhiều năm. Consts sẽ yêu cầu bình luận cho điều đó, ngoài ra họ sẽ yêu cầu tăng và giảm mã.


4

Với định nghĩa tôi mất an toàn loại

Không cần thiết...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

và với enum tôi mất một số không gian (số nguyên)

Không nhất thiết - nhưng bạn phải rõ ràng tại các điểm lưu trữ ...

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

và có lẽ phải sử dụng khi tôi muốn thực hiện thao tác bitwise.

Bạn có thể tạo các toán tử để loại bỏ nỗi đau đó:

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

Với const tôi nghĩ rằng tôi cũng mất an toàn kiểu vì một uint8 ngẫu nhiên có thể bị nhầm lẫn.

Điều tương tự cũng có thể xảy ra với bất kỳ cơ chế nào sau đây: kiểm tra phạm vi và giá trị thường trực giao để loại an toàn (mặc dù loại do người dùng xác định - tức là các lớp của riêng bạn - có thể thực thi "bất biến" về dữ liệu của họ). Với enum, trình biên dịch có thể tự do chọn một loại lớn hơn để lưu trữ các giá trị và một biến enum chưa được khởi tạo, bị hỏng hoặc chỉ bị lỗi vẫn có thể diễn giải mô hình bit của nó như một con số mà bạn không mong đợi - so sánh không bằng với bất kỳ các định danh liệt kê, bất kỳ sự kết hợp nào của chúng và 0.

Có cách nào khác sạch hơn không? / Nếu không, bạn sẽ sử dụng cái gì và tại sao?

Chà, cuối cùng, kiểu bit C đã thử và đáng tin cậy HOẶC liệt kê hoạt động khá tốt khi bạn có các trường bit và toán tử tùy chỉnh trong ảnh. Bạn có thể cải thiện hơn nữa sự mạnh mẽ của mình với một số chức năng xác nhận và xác nhận tùy chỉnh như trong câu trả lời của mat_geek; các kỹ thuật thường áp dụng như nhau để xử lý các giá trị chuỗi, int, double, v.v.

Bạn có thể lập luận rằng đây là "sạch hơn":

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

Tôi thờ ơ: các bit dữ liệu đóng gói chặt chẽ hơn nhưng mã tăng lên đáng kể ... phụ thuộc vào số lượng đối tượng bạn có và lamdbas - đẹp như chúng - vẫn lộn xộn và khó lấy hơn so với OR bitwise.

BTW / - đối số về IMHO khá yếu của IMHO - được ghi nhớ tốt nhất như là một sự cân nhắc nền tảng thay vì trở thành một lực lượng quyết định chi phối; chia sẻ một mutex trên các bitfield là một thực tế có khả năng hơn ngay cả khi không biết về việc đóng gói của chúng (mutexes là các thành viên dữ liệu tương đối cồng kềnh - Tôi phải thực sự lo lắng về hiệu suất để xem xét có nhiều mutexes trên các thành viên của một đối tượng và tôi sẽ xem xét cẩn thận đủ để nhận thấy chúng là các trường bit). Bất kỳ loại kích thước từ phụ nào cũng có thể có cùng một vấn đề (ví dụ a uint8_t). Dù sao, bạn có thể thử các hoạt động kiểu so sánh và hoán đổi nguyên tử nếu bạn mong muốn có tính đồng thời cao hơn.


1
+1 Tốt. Nhưng operator|nên chuyển sang một kiểu số nguyên ( unsigned int) trước lệnh |. Khác, operator|ý chí sẽ gọi đệ quy chính nó và gây ra tràn ngăn xếp thời gian chạy. Tôi đề nghị : return RecordType( unsigned(lhs) | unsigned(rhs) );. Chúc mừng
olibre

3

Ngay cả khi bạn phải sử dụng 4 byte để lưu trữ một enum (Tôi không quen thuộc với C ++ - Tôi biết bạn có thể chỉ định loại cơ bản trong C #), nó vẫn có giá trị - sử dụng enum.

Trong thời đại ngày nay của các máy chủ có GB bộ nhớ, những thứ như 4 byte so với 1 byte bộ nhớ ở cấp ứng dụng nói chung không thành vấn đề. Tất nhiên, nếu trong tình huống cụ thể của bạn, việc sử dụng bộ nhớ rất quan trọng (và bạn không thể sử dụng C ++ để sử dụng một byte để sao lưu enum), thì bạn có thể xem xét tuyến đường 'const const'.

Vào cuối ngày, bạn phải tự hỏi mình, có đáng để duy trì việc sử dụng 'const const' cho 3 byte tiết kiệm bộ nhớ cho cấu trúc dữ liệu của bạn không?

Một điều khác cần ghi nhớ - IIRC, trên x86, các cấu trúc dữ liệu được căn chỉnh 4 byte, vì vậy trừ khi bạn có một số phần tử độ rộng byte trong cấu trúc 'bản ghi' của mình, điều đó thực sự không quan trọng. Kiểm tra và chắc chắn rằng nó sẽ thực hiện trước khi bạn đánh đổi khả năng duy trì hiệu suất / không gian.


Bạn có thể chỉ định loại cơ bản trong C ++, kể từ phiên bản ngôn ngữ C ++ 11. Cho đến lúc đó, tôi tin rằng nó "ít nhất đủ lớn để lưu trữ và được sử dụng như một trường bit cho tất cả các điều tra viên được chỉ định, nhưng có lẽ inttrừ khi nó quá nhỏ". [Nếu bạn không chỉ định loại cơ bản trong C ++ 11, thì nó sử dụng hành vi kế thừa. Ngược lại, enum classkiểu cơ bản của C ++ 11 mặc định rõ ràng intnếu không được chỉ định khác.]
Thời gian của Justin - Phục hồi lại

3

Nếu bạn muốn loại an toàn của các lớp, với sự tiện lợi của cú pháp liệt kê và kiểm tra bit, hãy xem xét Nhãn an toàn trong C ++ . Tôi đã làm việc với tác giả, và anh ấy khá thông minh.

Hãy coi chừng, mặc dù. Cuối cùng, gói này sử dụng các mẫu macro!


Có vẻ như quá mức cho ứng dụng nhỏ của tôi. nhưng nó có vẻ như là một giải pháp tốt
Milan Babuškov

2

Bạn có thực sự cần phải vượt qua các giá trị cờ như một tổng thể khái niệm hay bạn sẽ có rất nhiều mã trên mỗi cờ? Dù bằng cách nào, tôi nghĩ rằng việc này là lớp hoặc cấu trúc của bitfield 1 bit thực sự có thể rõ ràng hơn:

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

Sau đó, lớp bản ghi của bạn có thể có một biến thành viên RecordFlag struct, các hàm có thể lấy các đối số của kiểu struct RecordFlag, v.v ... Trình biên dịch nên đóng gói các bitfield lại với nhau, tiết kiệm không gian.


Đôi khi như một toàn thể, đôi khi như cờ. Và, tôi cũng cần kiểm tra nếu một cờ nhất định được đặt (khi tôi chuyển toàn bộ).
Milan Babuškov

tốt, khi riêng biệt, chỉ cần yêu cầu một int. Khi cùng nhau, vượt qua cấu trúc.
wnoise

Nó sẽ không tốt hơn. Truy cập vào các trường bit chậm hơn bất cứ điều gì khác.
paercebal

Có thật không? Bạn nghĩ rằng trình biên dịch sẽ tạo ra các mã khác nhau đáng kể để kiểm tra các trường bit so với việc xử lý bit thủ công? Và rằng nó sẽ chậm hơn đáng kể? Tại sao? Điều duy nhất bạn không thể thực hiện một cách dễ dàng là che dấu nhiều cờ cùng một lúc.
wnoise

Chạy một bài kiểm tra đọc đơn giản, tôi nhận được 5,50-5,58 giây cho mặt nạ bit so với 5,45-5,59 để truy cập trường bit. Khá nhiều không thể phân biệt.
wnoise

2

Tôi có lẽ sẽ không sử dụng một enum cho loại điều này trong đó các giá trị có thể được kết hợp với nhau, điển hình hơn là các trạng thái loại trừ lẫn nhau.

Nhưng dù bạn sử dụng phương pháp nào, để làm rõ hơn rằng đây là các giá trị là các bit có thể được kết hợp với nhau, hãy sử dụng cú pháp này cho các giá trị thực tế thay thế:

#define X_NEW      (1 << 0)
#define X_DELETED  (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

Sử dụng dịch chuyển sang trái giúp chỉ ra rằng mỗi giá trị được dự định là một bit, ít có khả năng sau này ai đó sẽ làm điều gì đó sai như thêm một giá trị mới và gán cho nó một giá trị là 9.


1
Có đủ tiền lệ cho điều đó, đặc biệt là trong các hằng số cho ioctl (). Tôi thích sử dụng các hằng hex, mặc dù: 0x01, 0x02, 0x04, 0x08, 0x10, ...
Jonathan Leffler

2

Dựa trên KISS , độ gắn kết cao và khớp nối thấp , hãy hỏi những câu hỏi này -

  • Ai cần biết? lớp tôi, thư viện của tôi, lớp khác, thư viện khác, bên thứ 3
  • Tôi cần cung cấp mức độ trừu tượng nào? Liệu người tiêu dùng có hiểu các hoạt động bit.
  • Tôi có phải giao diện từ VB / C # vv không?

Có một cuốn sách tuyệt vời " Thiết kế phần mềm C ++ quy mô lớn ", cuốn sách này quảng bá các loại cơ sở bên ngoài, nếu bạn có thể tránh một phụ thuộc tệp / giao diện tiêu đề khác mà bạn nên thử.


1
a) 5-6 lớp. b) chỉ có tôi, đó là dự án một người c) không giao
thoa

2

Nếu bạn đang sử dụng Qt, bạn nên tìm QFlags . Lớp QFlags cung cấp một cách an toàn kiểu lưu trữ các kết hợp OR của các giá trị enum.


Không, không có Qt. Trên thực tế, đó là một dự án wxWidgets.
Milan Babuškov

0

Tôi thà đi với

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Đơn giản bởi vì:

  1. Nó sạch hơn và nó làm cho mã có thể đọc và bảo trì.
  2. Nó hợp lý nhóm các hằng số.
  3. Thời gian của lập trình viên là quan trọng hơn, trừ khi công việc của bạn lưu 3 byte đó.

Chà, tôi có thể dễ dàng có một triệu bản ghi của lớp, vì vậy nó có thể quan trọng. OTOH, đó chỉ là sự khác biệt giữa 1MB và 4 MB, vì vậy có lẽ tôi không nên lo lắng.
Milan Babuškov

@Vivek: Bạn đã xem xét giới hạn chiều rộng số nguyên chưa? Đặc biệt trước C ++ 11.
dùng2672165

0

Không phải tôi thích kỹ sư quá mức mọi thứ nhưng đôi khi trong những trường hợp này có thể đáng để tạo một lớp (nhỏ) để gói gọn thông tin này. Nếu bạn tạo một RecordType lớp thì nó có thể có các hàm như:

void setDelatted ();

void ClearDelatted ();

bool isDelatted ();

vv ... (hoặc bất cứ điều gì phù hợp với quy ước)

Nó có thể xác nhận các kết hợp (trong trường hợp không phải tất cả các kết hợp đều hợp pháp, ví dụ: nếu cả 'mới' và 'bị xóa' không thể được đặt cùng một lúc). Nếu bạn chỉ sử dụng mặt nạ bit, v.v. thì mã đặt trạng thái cần xác thực, một lớp cũng có thể gói gọn logic đó.

Lớp cũng có thể cung cấp cho bạn khả năng đính kèm thông tin ghi nhật ký có ý nghĩa vào từng trạng thái, bạn có thể thêm một hàm để trả về một chuỗi đại diện của trạng thái hiện tại, v.v. (hoặc sử dụng toán tử phát trực tuyến '<<').

Đối với tất cả những gì nếu bạn lo lắng về việc lưu trữ, bạn vẫn có thể có lớp chỉ có thành viên dữ liệu 'char', vì vậy chỉ mất một lượng lưu trữ nhỏ (giả sử nó không phải là ảo). Tất nhiên tùy thuộc vào phần cứng, vv bạn có thể có vấn đề căn chỉnh.

Bạn có thể có các giá trị bit thực tế không hiển thị với phần còn lại của 'thế giới' nếu chúng nằm trong một không gian tên ẩn danh bên trong tệp cpp chứ không phải trong tệp tiêu đề.

Nếu bạn thấy rằng mã sử dụng enum / # xác định / bitmask, v.v ... có rất nhiều mã 'hỗ trợ' để xử lý các kết hợp không hợp lệ, thì việc ghi nhật ký vv sau đó đóng gói trong một lớp có thể đáng xem xét. Tất nhiên, hầu hết các vấn đề đơn giản đều tốt hơn với các giải pháp đơn giản ...


Thật không may, khai báo phải ở trong một tệp .h vì nó được sử dụng trên toàn dự án (được sử dụng bởi một số lớp 5-6).
Milan Babuškov
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.