我有一个枚举类,如下所示:
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::none
、subset_type::exclude
和operator~
的支持作为练习保留。
你可以做一些事情的组合来实现这一点,借用别人的评论和答案。
- 使用
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
的人的年龄属性 很想听听关于该方法的一些想法。