将 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 &amp;(*it); 局部变量返回地址UB @billz:错误。 &amp;(*it) 是向量内元素的地址。你在想&amp;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-&gt;DoSomething(); 而不会遭受两次搜索。 @MarsonMao,你可以这样做。但是,将迭代器存储在函数之外作为全局变量并不是一个好的设计决策。 是的,我知道,实际上向量和迭代器以及这些方法将被包装在一个类中;向量和迭代器将是私有成员。为了简单起见,我将它们设为全局。 @MarsonMao,那还不错。【参考方案2】:

并且非常肯定一定有一个

已经给出了答案:如果不能确定,不检查就不能处理返回值。

但是这种编程看起来像一个旧的 posix 接口,必须检查每个 retval。在 c++ 中,您可以从 stl 获得更多功能,而无需自己编写此类代码。我建议你看看&lt;algorithm&gt;。考虑仿函数模式,您可以直接将操作赋予搜索功能。看看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(&amp;MyClass::condition),或者甚至是免费功能bool condition(MyClass const&amp;) 是的,这是一种简化。一个用例可以通过一些id找到MyClass,然后lambda可以测试mc.id() == id。这更有意义吗?但我仍然无法理解谓词的意义何在?我的意思是我仍然必须返回MyClass,对吗?你的意思是我应该返回MyClass 而不提供GetMyClass() @MarsonMao:我的意思是你甚至不应该有一个函数GetMyClass。只需致电std::find_if。而且你不需要mc.id()==someId 的包装器,这对于 lambda 来说已经足够清楚了。自定义谓词可能对 mc.condition1() &amp;&amp; !mc.condition2() &amp;&amp; (mc.id() &amp; mc.mask()) == someID 之类的东西有用 我明白了。所以你建议每个调用站点直接写find_if而不是调用GetMyClass?但这会不会有点太多余了?我的意思是,MyClass&amp; mc = GetMyClassManager().GetMyClass(someID) 不是比MyClass&amp; ms = *(std::find_if(GetMyClassManager().GetMyClasses().begin(), GetMyClassManager().GetMyClasses().end(), [](MyClass&amp; mc)return mc.id() == someID;) 容易吗?即使我想直接使用std::find_if,我也必须将lambda 存储在std::function 的某个地方,比如在MyClassManager 内部,对吧?

以上是关于将 Get() 拆分为容器的 Has() 和 Get() 有啥好处吗?的主要内容,如果未能解决你的问题,请参考以下文章

拆分容器有三个面板

将项目拆分为动态列的纯 CSS 解决方案

通用电气GE微服务实践:在容器中部署有状态应用

'str' object has no attribute 'get' 错误解决方案

将 Python MeshGrid 拆分为单元格

拆分 HTML 页面,以便打印机将其拆分为单独的页面