C ++でSafe(R)Bitflagsを入力しますか?
-
26-09-2019 - |
質問
古いC ++コードを修正しながら、私はいくつかに出くわしました Bitflags 列挙として定義されています。
enum FooFlags
{
FooFlag1 = 1 << 0,
FooFlag2 = 1 << 1,
FooFlag3 = 1 << 2
// etc...
};
これは珍しいことではありませんが、フラグを組み合わせ始めるとすぐに、タイプ情報が失われることを悩ませました。
int flags = FooFlag1 | FooFlag2; // We've lost the information that this is a set of flags relating to *Foo*
いくつかの検索は、私がそうではないことを示しました それだけ 1 これに悩まされました。
1つの選択肢は、フラグを#definesまたはconst Integralsとして宣言することです。したがって、ビットワイズ操作はタイプを変換しません(おそらく)。これの問題は、INTまたは他の列挙を介して、無関係なフラグに巻き込まれることができることです。
私はよく知っています std :: bitset と boost :: dynamic_bitset, 、しかし、どちらも私の問題に対処するようには設計されていません。私が探しているのはC#のようなものです flagsattribute.
私の質問は、Bitflagsの(より多くの)タイプの安全なセットに他にどのようなソリューションがあるかということです。
以下に自分のソリューションを投稿します。
解決 2
VS2010の現在のバージョンで許可されているC ++ 0xの要素を使用して、以下に私自身のソリューションです。
#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();
}
インターフェイスは後にモデル化されます std :: bitset. 。私の目的は、タイプの安全性と最小限のオーバーヘッドのC ++の精神に忠実であることでした。私の実装に関するフィードバックを歓迎します。
これが最小限の例です:
#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;
}
他のヒント
適切なタイプされた結果を返す列挙タイプのオペレーターをオーバーロードできます。
inline FooFlags operator|(FooFlags a, FooFlags b) {
return static_cast<FooFlags>(+a | +b);
}
理論的に安全であるためには、列挙タイプの範囲がすべての組み合わせをキャッチするために保証されるように、可能な限り最高値を手動で宣言する必要があることに注意する必要があります。
- 実際にはそれは必要ありません:列挙の範囲は常にすべての組み合わせをキャッチすることができます。なぜなら、列挙の範囲の最高の正値は常に
(2^N)-1
最初はN
最高の列挙器を表すことができます。その値にはすべてのビットがあります1。
C ++ 11バージョンを追加するかもしれないと思った 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));
}
C ++ 11バージョンがサポートしている場合、これは最高の候補者になると思います constexpr