在C++中向枚举添加"all"选项的好设计是什么?



我有一个枚举类,如下所示:

enum class Age
{
    Eleven,
    Twelve,
    Thirteen
};

然后我有一个名为vector<Person> GetPeopleOfAge(Age age)的方法。 什么是好的设计,以便开发人员可以调用它并得到 11、12 和 13 的人? 我可以称它三次,这很糟糕,但我确实想提一下我考虑过它。 我可以添加一个All枚举并在我的方法中进行检查,但我不喜欢用像 All 这样的枚举污染枚举的想法,只是为了让我的情况起作用。 我知道这是解决这个问题的常用方法,有些人可能不同意我的观点,但对我来说,这感觉很笨拙,正在寻找替代方案。 也许我应该使用枚举以外的其他东西?

无论All是在enum中显式捕获还是由另一种机制隐式捕获,您都必须处理抽象。鉴于此,我发现最好明确处理它。

可以使用使用枚举值的既定方法,以便可以使用按位 OR 运算符组合枚举。

enum Age : unsigned int
{
   Eleven   = 000001,
   Twelve   = 000010,
   Thirteen = 000100,
   All      = 000111
};

然后,您可以使用

// Get all the people of age 11
auto ret1 = GetPeopleOfAge(Age::Eleven);
// Get people of all ages
auto ret2 = GetPeopleOfAge(Age::All);
// Get all the people aged 11 or 13
auto ret3 = GetPeopleOfAge(Age::Eleven | Age::Thirteen);

显而易见的解决方案是转储enum: 年龄是一个连续的概念,可以量化,但永远不会完全枚举(您支持的最高年龄是多少?120?130?200?1000?您的选择要么不合理地大,要么有可能排除真人!此外,当您谈论年龄时,您经常需要选择年龄范围

因此,年龄应该是一个int或一个float。并且您的GetPeopleOfAge()函数应声明为

vector<Person> GetPeopleOfAge(int minAge, int maxAge);

无需用enum使事情复杂化.

一种选择是将过滤器参数设置为可选:

vector<Person> GetPeopleOfAge(std::optional<Age> age = {})

然后,在函数内部,使用 if (age) 检查是否应执行基于年龄的过滤。

不过,这个函数可能应该重命名,因为它并不总是给特定年龄的人;有时,它给所有人。

你可以

GetPeopleOfAge可变参数(initializer_list也可以工作(并给它一个更好的名字:

template <typename... Ts>
vector<Person> GetPeopleOfAnyAge(Ts... ages);
    // Return a vector of all people having any of `ages...`.

用法:

const auto people = GetPeopleOfAnyAge(
    Age::Eleven, Age::Twelve, Age::Thirteen);

如果您经常获得所有年龄段的人,则可以创建一个包装器:

const auto getAllPeople = []
{ 
    return GetPeopleOfAnyAge(
        Age::Eleven, Age::Twelve, Age::Thirteen); 
};

我会使用谓词来过滤返回的列表。 然后,调用方可以为子集使用他们想要的任何条件。 (结合弗朗索瓦和维托里奥的想法。

例:

#include <algorithm>
#include <initializer_list>
#include <iostream>
#include <ostream>
#include <string>
#include <vector>
using std::cout;
using std::endl;
using std::function;
using std::initializer_list;
using std::ostream;
using std::string;
using std::vector;
enum class Gender { unknown, male, female };
class Person {
  string name;
  int age;
  Gender gender;
public:
  Person(string n, int a, Gender g) : name{move(n)}, age{a}, gender{g} { }
  string Name() const { return name; }
  int Age() const { return age; }
  Gender Gender() const { return gender; }
};
ostream& operator<<(ostream& o, Person const& person) {
  o << person.Name() << "(" << person.Age() << ", ";
  switch (person.Gender()) {
    case Gender::unknown: o << "?"; break;
    case Gender::male: o << "boy"; break;
    case Gender::female: o << "girl"; break;
  }
  o << ")";
  return o;
}
class People {
  vector<Person> people;
public:
  People(initializer_list<Person> l) : people{l} { }
  vector<Person> GetPeople(function<bool(Person const&)> predicate);
};
vector<Person> People::GetPeople(function<bool(Person const&)> predicate) {
  vector<Person> result;
  for (auto const& person : people) {
    if (predicate(person)) {
      result.push_back(person);
    }
  }
  return result;
}
ostream& operator<<(ostream& o, vector<Person> const& vector_of_person) {
  char const* sep = "";
  for (auto const& person : vector_of_person) {
    o << sep << person;
    sep = ", ";
  }
  return o;
}
int main() {
  auto const b = Gender::male;
  auto const g = Gender::female;
  People people = {{"Anu", 13, g}, {"Bob", 11, b}, {"Cat", 12, g}, {"Dex", 11, b}, {"Eli", 12, b}};
  auto ageUnder13 = [](Person const& p) { return p.Age() < 13; };
  cout << people.GetPeople(ageUnder13) << endl;
  auto everyone = [](Person const& p) { return true; };
  cout << people.GetPeople(everyone) << endl;
  auto boys = [](Person const& p) { return p.Gender() == Gender::male; };
  cout << people.GetPeople(boys) << endl;
  return EXIT_SUCCESS;
}

虽然 R Sahu 在同一个想法上比我更快,但回到你的评论:

看起来我看到的每个解决方案都不是很完美。这个,枚举不是类型安全的 [...]

如果你出于任何原因想要保留作用域枚举,你可以自己定义必要的运算符,见下文(一点工作,承认 - 好吧,"完美"呢?关于All值:好吧,没有人说你需要包含它,这是 R Sahu 的偏好(而我的则相反......(——最后,这是一个用例问题,不过......

enum class E
{
    E0 = 1 << 0,
    E1 = 1 << 1,
    E2 = 1 << 2,
    E3 = 1 << 3,
};
E operator|(E x, E y)
{
    return static_cast<E>
    (
            static_cast<std::underlying_type<E>::type>(x)
            |
            static_cast<std::underlying_type<E>::type>(y)
    );
}
E operator&(E x, E y)
{
    return static_cast<E>
    (
            static_cast<std::underlying_type<E>::type>(x)
            &
            static_cast<std::underlying_type<E>::type>(y)
    );
}
bool operator==(E x, std::underlying_type<E>::type y)
{
    return static_cast<std::underlying_type<E>::type>(x) == y;
}
bool operator!=(E x, std::underlying_type<E>::type y)
{
    return !(x == y);
}
bool operator==(std::underlying_type<E>::type y, E x)
{
    return x == y;
}
bool operator!=(std::underlying_type<E>::type y, E x)
{
    return x != y;
}
void f(E e)
{
    E person = E::E1;
    if((person & e) != 0)
    {
        // add to list...
    }
}
int main(int argc, char *argv[])
{
    f(E::E0 | E::E1);
    return 0;
}

为什么不疯呢?

enum class subset_type {
  include, all
};
struct include_all_t { constexpr include_all_t() {} };
constexpr include_all_t include_all {};
template<class E>
struct subset {
  subset_type type = subset_type::include;
  std::variant<
    std::array<E, 0>, std::array<E, 1>, std::array<E, 2>, std::array<E, 3>, std::array<E, 4>,
    std::vector<E>
  > data = std::array<E,0>{};
  // check if e is in this subset:
  bool operator()( E e ) const {
    // Everything is in the all subset:
    if(type==subset_type::all)
      return true;
    // just do a linear search.  *this is iterable:
    for (E x : *this)
      if (x==e) return true;
    return false;
  }
  // ctor, from one, subset of one:
  subset(E e):
    type(subset_type::include),
    data(std::array<E, 1>{{e}})
  {}
  // ctor from nothing, nothing:
  subset() = default;
  // ctor from {list}, those elements:
  subset(std::initializer_list<E> il):
    type(subset_type::include)
  {
    populate(il);
  }
  // ctor from include_all:
  subset(include_all_t):type(subset_type::all) {}
  // these 3 methods are only valid to call if we are not all:
  E const* begin() const {
    return std::visit( [](auto&& x){ return x.data(); }, data );
  }
  std::size_t size() const {
    return std::visit( [](auto&& x){ return x.size(); }, data );
  }
  E const* end() const {
    return begin()+size();
  }
  // this method is valid if all or not:
  bool empty() const {
    return type!=subset_type::all && size()==0;
  }
  // populate this subset with the contents of srcs, as iterables:
  template<class...Src>
  void populate(Src const&...srcs) {
    std::size_t count = (std::size_t(0) + ... + srcs.size());
    // used to move runtime count to compiletime N:
    auto helper = [&](auto N)->subset& {
      std::array<E, N> v;
      E* ptr = v.data();
      auto add_src = [ptr](auto& src){
        for (E x:src)
          *ptr++ = x;
      };
      (add_src(srcs),...);
      this->data = v;
    };
    // handle fixed size array cases:
    switch(count) {
      case 0: return helper(std::integral_constant<std::size_t, 0>{});
      case 1: return helper(std::integral_constant<std::size_t, 1>{});
      case 2: return helper(std::integral_constant<std::size_t, 2>{});
      case 3: return helper(std::integral_constant<std::size_t, 3>{});
      case 4: return helper(std::integral_constant<std::size_t, 4>{});
      default: break;
    };
    // otherwise use a vector:
    std::vector<E> vec;
    vec.reserve(count);
    auto vec_helper = [&](auto&& c){
      for (E x:c) vec.push_back(c);
    };
    (vec_helper(srcs), ...);
    data = std::move(vec);
  }
  // because what is a set without a union operator?
  friend subset& operator|=( subset& lhs, subset const& rhs ) {
    if (lhs.type==subset_type::all) return lhs;
    if (rhs.type==subset_type::all) {
      lhs.type = subset_type::all
      return lhs;
    }
    populate( lhs, rhs );
    return lhs;
  }
  friend subset operator|( subset lhs, subset const& rhs ) {
    lhs |= rhs;
    return std::move(lhs);
  }
};

C++17 可能是错别字。

std::vector<Person> GetPeopleOfAge(subset<Age> age)

你可以用 Age::Eleven 来称呼它,也可以用 include_all 来称呼它,或者用 {} 来称呼它,或者用 {Age::Eleven, Age::Twelve} 来称呼它。

它使用一个小的缓冲区优化来处理多达 4 个元素。

如果不处于all模式,则可以使用基于范围的 for 循环循环遍历子集中的元素。

添加对operator&subset_type::nonesubset_type::excludeoperator~的支持作为练习保留。

你可以做一些事情的组合来实现这一点,借用别人的评论和答案。

  • 使用 std::tuple 创建可变参数模板
  • enum(s)上使用位逻辑 - 在许多 API 函数中看到的典型设置,以使用多个设置,例如: glClearColor( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); 这些通常称为使用位域的管道。
  • 创建一个对象的实例,该实例是每个对象的单个向量的结果,可以说使用该向量accumulated使用上述机制。

这种性质的东西可能对你有用:

enum class Gender {
    male,
    female,
    both,
    other
};
class age_constraints {
public:
    const static unsigned min_age { 1 };
    const static unsigned max_age { 130 };
protected:
    age_constraints() = default;
}; typedef age_constraints age_range;
class attributes {
public:
    std::string firstName;
    std::string middleNameOrInitial;
    std::string lastName;
    unsigned age;
    Gender gender;
    attributes() = default;
    attributes( const std::string& first, const std::string& middle, const std::string& last, 
                const unsigned& ageIn, const Gender& gend ) :
        firstName( first ),
        middleNameOrInitial( middle ),
        lastName( last ),
        age( ageIn ),
        gender( gend ) 
    {}
};
class Person {
private:
    attributes attribs;
public:
    Person() = default;
    explicit Person( const attributes& attribsIn ) : attribs( attribsIn ) {}
    Person( const std::string& firstName, const std::string& middle, const std::string& lastName, 
            const unsigned& age, const Gender& gender ) :
        attribs( firstName, middle, lastName, age, gender ) {}
    // other methods
};
class Database {
public:
    const static age_range range;
private:
    std::vector<std::shared_ptr<Person>> peopleOnFile;
public:
    Database() = default;
    void addPerson( const Person&& person ) {
        peopleOnFile.emplace_back( new Person( person ) );
    }
    template<bool all = false>
    std::vector<Person> getPeopleByAges( unsigned minAge, unsigned maxAge, unsigned ages... ) {
        std::tuple<ages> agesToGet( ages... );
        std::vector<Person> peopleToGet;
        // compare tuple ages with the ages in Person::attributes - don't forget that you can use the age_constraints for assistance
        // populate peopleToGet with appropriate age range
            return peopleToGet;
    }
    template<bool all = true>
    std::vector<Person> getPeopleByAges() {
        return peopleOnFile;
    }
};

在上面的数据库示例中:我在伪代码中所做的工作表明,代码的繁重或批量工作是在某个范围内搜索的函数版本中完成的,其中查找 all 的重载方法不接受任何参数,只返回完整的向量。

我从我看到的所有答案中汲取了一些想法,这是我能想到的最佳解决方案。

class People
{
   public:
   GetPeopleOfAllAges()
   {
       GetPeopleOfAge();
   }
   private:
   GetPeopleOfAge(Age age = NULL)
   {
       if (age == NULL ||
          *age == Person.age)
       {
           // Add Person to list.
       }
   }
}

在这种情况下,NULL 意味着给我所有不理想的东西,但至少它隐藏在 UI 层之外,我没有违反无法All的人的年龄属性 很想听听关于该方法的一些想法。

最新更新