2010-11-19 28 views
22

Trong khi sửa đổi một số mã C++ cũ, tôi chạy qua một số bitflags được định nghĩa là enums.Nhập bitflags an toàn (r) vào C++?

enum FooFlags 
{ 
    FooFlag1 = 1 << 0, 
    FooFlag2 = 1 << 1, 
    FooFlag3 = 1 << 2 
    // etc... 
}; 

Điều này không có gì lạ, nhưng nó làm phiền tôi rằng ngay sau khi bạn bắt đầu kết hợp cờ, bạn sẽ mất thông tin loại.

int flags = FooFlag1 | FooFlag2; // We've lost the information that this is a set of flags relating to *Foo* 

Một số tìm kiếm trên SO cho thấy tôi không phải là onlyone phiền của thành viên này.

Một cách khác là khai báo cờ là #defines hoặc const integrals, do đó các thao tác bitwise sẽ không biến đổi loại (có thể). Vấn đề với điều này là nó cho phép bit của chúng tôi thiết lập để commingle với cờ không liên quan, thông qua ints hoặc enums khác.

Tôi quen thuộc với std::bitsetboost::dynamic_bitset nhưng không được thiết kế để giải quyết vấn đề của tôi. Những gì tôi đang tìm kiếm là một cái gì đó giống như C# 's FlagsAttribute.

Câu hỏi của tôi là, còn có giải pháp nào khác cho một tập hợp bitflags an toàn hơn?

Tôi sẽ đăng giải pháp của riêng mình bên dưới.

Trả lời

12

Đây là giải pháp của riêng tôi, sử dụng các yếu tố của C++ 0x rằng phiên bản hiện tại của VS2010 cho phép:

#include <iostream> 
#include <numeric> 
#include <string> 

#include <initializer_list> 

template <typename enumT> 
class FlagSet 
{ 
    public: 

     typedef enumT      enum_type; 
     typedef decltype(enumT()|enumT()) store_type; 

     // Default constructor (all 0s) 
     FlagSet() : FlagSet(store_type(0)) 
     { 

     } 

     // Initializer list constructor 
     FlagSet(const std::initializer_list<enum_type>& initList) 
     { 
      // This line didn't work in the initializer list like I thought it would. It seems to dislike the use of the lambda. Forbidden, or a compiler bug? 
      flags_ = std::accumulate(initList.begin(), initList.end(), store_type(0), [](enum_type x, enum_type y) { return x | y; }) 
     } 

     // Value constructor 
     explicit FlagSet(store_type value) : flags_(value) 
     { 

     } 

     // Explicit conversion operator 
     operator store_type() const 
     { 
      return flags_; 
     } 

     operator std::string() const 
     { 
      return to_string(); 
     } 

     bool operator [] (enum_type flag) const 
     { 
      return test(flag); 
     } 

     std::string to_string() const 
     { 
      std::string str(size(), '0'); 

      for(size_t x = 0; x < size(); ++x) 
      { 
       str[size()-x-1] = (flags_ & (1<<x) ? '1' : '0'); 
      } 

      return str; 
     } 

     FlagSet& set() 
     { 
      flags_ = ~store_type(0); 
      return *this; 
     } 

     FlagSet& set(enum_type flag, bool val = true) 
     { 
      flags_ = (val ? (flags_|flag) : (flags_&~flag)); 
      return *this; 
     } 

     FlagSet& reset() 
     { 
      flags_ = store_type(0); 
      return *this; 
     } 

     FlagSet& reset(enum_type flag) 
     { 
      flags_ &= ~flag; 
      return *this; 
     } 

     FlagSet& flip() 
     { 
      flags_ = ~flags_; 
      return *this; 
     } 

     FlagSet& flip(enum_type flag) 
     { 
      flags_ ^= flag; 
      return *this; 
     } 

     size_t count() const 
     { 
      // http://www-graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan 

      store_type bits = flags_; 
      size_t total = 0; 
      for (; bits != 0; ++total) 
      { 
       bits &= bits - 1; // clear the least significant bit set 
      } 
      return total; 
     } 

     /*constexpr*/ size_t size() const // constexpr not supported in vs2010 yet 
     { 
      return sizeof(enum_type)*8; 
     } 

     bool test(enum_type flag) const 
     { 
      return (flags_ & flag) > 0; 
     } 

     bool any() const 
     { 
      return flags_ > 0; 
     } 

     bool none() const 
     { 
      return flags == 0; 
     } 

    private: 

     store_type flags_; 

}; 

template<typename enumT> 
FlagSet<enumT> operator & (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs) 
{ 
    return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) & FlagSet<enumT>::store_type(rhs)); 
} 

template<typename enumT> 
FlagSet<enumT> operator | (const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs) 
{ 
    return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs) | FlagSet<enumT>::store_type(rhs)); 
} 

template<typename enumT> 
FlagSet<enumT> operator^(const FlagSet<enumT>& lhs, const FlagSet<enumT>& rhs) 
{ 
    return FlagSet<enumT>(FlagSet<enumT>::store_type(lhs)^FlagSet<enumT>::store_type(rhs)); 
} 

template <class charT, class traits, typename enumT> 
std::basic_ostream<charT, traits> & operator << (std::basic_ostream<charT, traits>& os, const FlagSet<enumT>& flagSet) 
{ 
    return os << flagSet.to_string(); 
} 

Giao diện được mô hình hóa sau khi std::bitset. Mục tiêu của tôi là đúng với các đặc điểm C++ về an toàn kiểu và tối thiểu (nếu có). Tôi hoan nghênh bất kỳ phản hồi nào về việc triển khai của tôi.

Dưới đây là một ví dụ nhỏ:

#include <iostream> 

enum KeyMod 
{ 
    Alt  = 1 << 0, // 1 
    Shift = 1 << 1, // 2 
    Control = 1 << 2 // 4 
}; 

void printState(const FlagSet<KeyMod>& keyMods) 
{ 
    std::cout << "Alt is "  << (keyMods.test(Alt)  ? "set" : "unset") << ".\n"; 
    std::cout << "Shift is " << (keyMods.test(Shift) ? "set" : "unset") << ".\n"; 
    std::cout << "Control is " << (keyMods.test(Control) ? "set" : "unset") << ".\n"; 
} 

int main(int argc, char* argv[]) 
{ 
    FlagSet<KeyMod> keyMods(Shift | Control); 

    printState(keyMods); 

    keyMods.set(Alt); 
    //keyMods.set(24); // error - an int is not a KeyMod value 
    keyMods.set(Shift); 
    keyMods.flip(Control); 

    printState(keyMods); 

    return 0; 
} 
+0

Bất kỳ cơ hội nào về ví dụ sử dụng cho việc triển khai của bạn? – Eric

+0

@Eric, tôi cảm thấy nó khá thẳng về phía trước. Bạn thật ra đang tim cai gi? – luke

+0

Một ví dụ đơn giản về khai báo một 'en' enum, khởi tạo một' FlagSet ', và sử dụng nó. Chắc chắn, tôi có thể làm việc nó ra, nhưng một ví dụ sẽ làm cho câu trả lời này tốt hơn. – Eric

22

Bạn có thể quá tải khai thác với nhiều loại liệt kê mà trả lại kết quả đánh máy thích hợp.

inline FooFlags operator|(FooFlags a, FooFlags b) { 
    return static_cast<FooFlags>(+a | +b); 
} 

Cần lưu ý rằng để trở thành lý thuyết an toàn, bạn nên khai báo bằng tay giá trị cao nhất có thể để nhiều kiểu liệt kê được đảm bảo để đón tất cả các kết hợp.

  • Trên thực tế đó là không cần thiết: phạm vi của một liệt kê sẽ luôn luôn có thể bắt tất cả các kết hợp, bởi vì giá trị dương cao nhất trong phạm vi của một liệt kê luôn là (2^N)-1 cho N đầu tiên có khả năng đại diện cho điều tra viên cao nhất. Giá trị đó có tất cả các bit 1.
+1

Tại sao '+ a' và' + b' thay vì chỉ 'a' và' b' không quan tâm? An toàn? –

+2

@sgolodetz nó sẽ nhập một đệ quy vô hạn (bằng cách gọi toán tử '| đang được xác định). –

+2

@Johannes Schaub: SO bạn đang sử dụng dấu + làm ẩn trong số nguyên. Tại sao không rõ ràng về nó và sử dụng static_cast <>()? –

7

Nghĩ rằng tôi có thể thêm vào một C++ 11 phiên bản dành cho enum class

FooFlags operator|(FooFlags a, FooFlags b) 
{ 
    typedef std::underlying_type<FooFlags>::type enum_type; 
    return static_cast<FooFlags>(static_cast<enum_type>(a) | static_cast<enum_type>(b)); 
} 

Nếu bạn C++ 11 phiên bản hỗ trợ nó, tôi đoán đây sẽ là một ứng cử viên cho constexpr