将 Get() 拆分为容器的 Has() 和 Get() 有啥好处吗?
Posted
技术标签:
【中文标题】将 Get() 拆分为容器的 Has() 和 Get() 有啥好处吗?【英文标题】:Is there any benefit to split Get() into Has() and Get() of containers?将 Get() 拆分为容器的 Has() 和 Get() 有什么好处吗? 【发布时间】:2014-07-30 04:30:17 【问题描述】:假设我有一个vector<MyClass>
和一个GetMyClass()
,比如:
std::vector<MyClass> mcs; // assume mcs has some elements...
MyClass* GetMyClass()
auto it = std::find_if(mcs.begin(), mcs.end(),
[](const MyClass& mc)
return mc.condition() == true;
);
if (it == mcs.end())
return nullptr;
else
return &(*it);
这很常规,用户调用它并检查返回值是否为nullptr
。但是如果用户忘记检查,就会发生不好的事情。 nullptr
检查很烦人,而且并不总是发生。此外,此功能是检查(如果有任何有效的MyClass
)和获取(一个MyClass
)的混合。
附注MyClass.condition() == true;
只是一个简化,实际情况可能会更复杂。
所以我偶然发现了两个函数:
bool HasMyClass()
auto it = std::find_if(mcs.begin(), mcs.end(),
[](const MyClass& mc)
return mc.condition() == true;
);
return (it == mcs.end());
MyClass& GetMyClass()
auto it = std::find_if(mcs.begin(), mcs.end(),
[](const MyClass& mc)
return mc.condition() == true;
);
return (*it);
如果用户想知道MyClass
的存在,那么他调用HasMyClass()
;
如果用户想要得到一个MyClass
,并且非常确定一定有,那么他直接调用GetMyClass()
。如果用户想要一个MyClass
但不确定,那么他会同时调用它们。
为了消除代码重复,避免冗余执行,我将其修改为:
std::vector<MyClass> mcs; // assume mcs has some elements...
// std::vector<MyClass>::iterator temp; // originally this
MyClass* temp = nullptr; // changed to this according to Hsi-Hung Shih's advice
bool HasMyClass()
auto it = std::find_if(mcs.begin(), mcs.end(),
[](const MyClass& mc)
return mc.condition() == true;
);
if (it == mcs.end())
temp = nullptr;
return false;
else
temp = &(*it);
return true;
MyClass& GetMyClass()
if (temp == nullptr)
HasMyClass();
MyClass& mc = (*temp);
temp = nullptr;
return mc;
我的问题是:
增加一个Has()
函数有什么好处吗?
如果是这样,这会减轻用户的负担吗?这会让代码使用的意图更加清晰吗?额外的复杂性值得吗?
谢谢!
【问题讨论】:
return MyClass.condition() == true;
??你是说return mc.condition() == true;
如果用户不确定并调用了两者,则搜索执行两次,而之前只执行一次。此外,您首先假设一个不确定的用户,但未能检查nullptr
的返回值。为什么你相信同一个用户不会打电话给Has()
?
你在 if (temp == mcs.end() 处遗漏了一些括号
return &(*it);
局部变量返回地址UB
@billz:错误。 &(*it)
是向量内元素的地址。你在想&it
。
【参考方案1】:
我建议保留GetMyClass
的原始实现。我会添加HasMyClass
作为一个便利功能,用于那些需要分叉的唯一信息的情况。
您可以使用GetMyClass
来实现HasMyClass
。这是一个简单的实现。
bool HasMyClass()
return (GetMyClass() != nullptr);
如果一个函数使用,
if ( HasMyClass() )
MyClass* c = GetMyClass();
将进行两次搜索。这是对函数的错误使用,但您无法阻止它们。
HasMyClass
的更好用法是:
if ( HasMyClass() )
// Follow path 1 (does not involve call to GetMyClass)
else
// Follow path 2 (does not involve call to GetMyClass either)
【讨论】:
但通常HasMyClass()
会带来GetMyClass()
,这就是为什么我试图在temp
迭代器的帮助下避免两次搜索,这样一个人可以调用if (!HasMyClass()) return; MyClass* c = GetMyClass(); c->DoSomething();
而不会遭受两次搜索。
@MarsonMao,你可以这样做。但是,将迭代器存储在函数之外作为全局变量并不是一个好的设计决策。
是的,我知道,实际上向量和迭代器以及这些方法将被包装在一个类中;向量和迭代器将是私有成员。为了简单起见,我将它们设为全局。
@MarsonMao,那还不错。【参考方案2】:
并且非常肯定一定有一个
已经给出了答案:如果不能确定,不检查就不能处理返回值。
但是这种编程看起来像一个旧的 posix 接口,必须检查每个 retval。在 c++ 中,您可以从 stl 获得更多功能,而无需自己编写此类代码。我建议你看看<algorithm>
。考虑仿函数模式,您可以直接将操作赋予搜索功能。看看for_each
和其他人。
http://www.cplusplus.com/reference/algorithm/for_each/ http://www.cplusplus.com/reference/algorithm/copy_if/
或者只是全部: http://www.cplusplus.com/reference/algorithm/
我相信您可以使用 stl 功能做很多事情。编写自己的搜索函数、访问它们、检查 retval 以及对值/迭代器执行某些操作通常不能通过手工编写的代码来完成。
第二次尝试可能是抛出 throw NotFound_t();
之类的异常
【讨论】:
是的,检查 retval 很烦人。但是我不太明白在这种情况下为什么要使用for_each
,请给我一些提示好吗?【参考方案3】:
检查返回值是否可能失败是常见的 API 设计,因此程序员应该熟悉它并且可以轻松使用它。如果他们忘记检查它,他们会知道何时取消引用它。
没有发现可以抛出异常,但是在容器中没有发现通常不是异常,最好不要抛出。
顺便说一句,您的代码也很危险,因为迭代器“temp”在您之前的查找之后可能无效。它可能是由于从向量中删除了项目。副作用不清楚的功能非常糟糕。
总之,让人们熟悉您的 API。不要为了让它看起来可爱而使代码复杂化,比如让人们不检查返回值是否有潜在的失败。
编辑
如果你的数据可以是 nullptr,你可以定义一个要返回的数据类,它可以指示失败状态。如果你很懒惰,你可以使用 std::pair 来做到这一点。
struct Data
bool is_failed;
MyClass* data;
;
【讨论】:
你说得对,我应该使用MyClass*
代替temp
,而不是迭代器。但是我想要实现的是为了方便用户,我认为在很多情况下检查返回值是烦人的和多余的。正如 Klaus 所说:“但这种编程看起来像一个旧的 posix 接口,必须检查每个 retval”。如果必须检查返回值,那么它应该尽可能清晰,就像“有或没有”的bool
非常简单。
而 nullptr
表示“有或没有”不够简单;如果容器确实允许将nullptr
存储在其中怎么办?
是的,我知道,这种模式很棒,在某些情况下我会使用这种模式。但在其他情况下,人们希望立即停止执行(以使事情更简单),而不是一系列无操作的调用。所以检查会有很大帮助。
如果你的 HasMyClass() 在向量中找不到任何东西,当用户调用 GetMyClass() 时会发生什么?它崩溃了......而不是让用户在取消引用 nullptr 时崩溃,您只需让它在您自己的函数中崩溃。好点了吗?
如果真想立即“停止执行”,那一定是异常情况。如果是这样,只需抛出一个异常。【参考方案4】:
通常的实现是更改接口以将指针作为参数传递并返回是否找到对象:
bool TryGetMyClass(MyClass* ptr)
auto it = std::find_if(mcs.begin(), mcs.end(),
[](const MyClass& mc)
return mc.condition() == true;
);
if (it == mcs.end())
ptr = nullptr;
return false;
else
ptr = &(*it);
return true;
使用示例:
if(TryGetMyClass(ptr))
... // Ok, ptr not null
else
... // Handle null pointer
【讨论】:
很有趣,我会研究的。也感谢unique_ptr
的建议。
@BenjaminLindley 我输入了快速、智能的 ptr reflex... 注释已删除,谢谢。【参考方案5】:
老实说,这些函数表明你只是没有通过包装 STL 方法实现任何目标。那些已经有一个完善的模式来沟通“未找到”,而 C++ 程序员已经熟悉了这一点。你的方法甚至让你感到困惑。
相反,您应该做的是提供合适的谓词以在std::find
中使用。
【讨论】:
你的意思是我提供给find_if
的lambda不够好?你能举个例子吗?谢谢!
@MarsonMao:为了这个问题,我认为这是一个简化。我会使用std::mem_fun(&MyClass::condition)
,或者甚至是免费功能bool condition(MyClass const&)
。
是的,这是一种简化。一个用例可以通过一些id
找到MyClass
,然后lambda可以测试mc.id() == id
。这更有意义吗?但我仍然无法理解谓词的意义何在?我的意思是我仍然必须返回MyClass
,对吗?你的意思是我应该返回MyClass
而不提供GetMyClass()
?
@MarsonMao:我的意思是你甚至不应该有一个函数GetMyClass
。只需致电std::find_if
。而且你不需要mc.id()==someId
的包装器,这对于 lambda 来说已经足够清楚了。自定义谓词可能对 mc.condition1() && !mc.condition2() && (mc.id() & mc.mask()) == someID
之类的东西有用
我明白了。所以你建议每个调用站点直接写find_if
而不是调用GetMyClass
?但这会不会有点太多余了?我的意思是,MyClass& mc = GetMyClassManager().GetMyClass(someID)
不是比MyClass& ms = *(std::find_if(GetMyClassManager().GetMyClasses().begin(), GetMyClassManager().GetMyClasses().end(), [](MyClass& mc)return mc.id() == someID;)
容易吗?即使我想直接使用std::find_if
,我也必须将lambda 存储在std::function
的某个地方,比如在MyClassManager
内部,对吧?以上是关于将 Get() 拆分为容器的 Has() 和 Get() 有啥好处吗?的主要内容,如果未能解决你的问题,请参考以下文章