是否有与“为空”相对的简明扼要?

问题描述 投票:20回答:11

字符串类的接口通常具有名为IsEmptyVCL)或emptySTL)的方法。这是绝对合理的,因为这是特例,但是使用这些方法的代码通常必须否定该谓词,从而导致“ optical(甚至心理上)overhead”(感叹号不是非常明显,尤其是在括号后面)。例如,查看此(简化)代码:

/// format an optional time specification for output
std::string fmtTime(const std::string& start, const std::string& end)
{
    std::string time;
    if (!start.empty() || !end.empty()) {
        if (!start.empty() && !end.empty()) {
            time = "from "+start+" to "+end;
        } else {
            if (end.empty()) {
                time = "since "+start;
            } else {
                time = "until "+end;
            }
        }
    }
    return time;
}

它具有四个否定,因为空白的情况就是要跳过的情况。在设计接口时,我经常观察到这种否定,这是[[不是大问题,但是很烦人。我只希望支持编写易于理解的代码。希望您能理解我的观点。

也许我只是被盲目击中:您将如何解决上述问题?


Edit:

阅读一些评论后,我认为有必要说原始代码使用了VCL的类System::AnsiString。此类提供了一个IsEmpty方法,该方法非常易读: if (text.IsEmpty()) { /* ... */ } // read: if text is empty ...
如果未否定:

if (!text.IsEmpty()) { /* ... */} // read: if not text is empty ...

...而不是

如果文本不为空。我认为字面意思is最好放在读者的幻想中,以使否定也很好地起作用。好吧,也许不是一个普遍的问题...

c++ string coding-style api-design readability
11个回答
27
投票
在大多数情况下,您可以颠倒ifelse的顺序以清除代码:

const std::string fmtTime(const std::string& start, const std::string& end) { std::string time; if (start.empty() && end.empty()) { return time; } if (start.empty() || end.empty()) { if (end.empty()) { time = "since "+start; } else { time = "until "+end; } } else { time = "from "+start+" to "+end; } return time; }

甚至在进行一些重构后更加干净:

std::string fmtTime(const std::string& start, const std::string& end) { if (start.empty() && end.empty()) { return std::string(); } if (start.empty()) { return "until "+end; } if (end.empty()) { return "since "+start; } return "from "+start+" to "+end; }

并且为了最终的紧凑性(尽管我更喜欢以前的版本,因为它易于阅读):

std::string fmtTime(const std::string& start, const std::string& end) { return start.empty() && end.empty() ? std::string() : start.empty() ? "until "+end : end.empty() ? "since "+start : "from "+start+" to "+end; }


另一种可能性是创建一个辅助函数:

inline bool non_empty(const std::string &str) { return !str.empty(); } if (non_empty(start) || non_empty(end)) { ... }


2
投票
not

((有关替代令牌,请参阅标准中的const std::string fmtTime(const std::string& start, const std::string& end) { std::string time; if (not start.empty() or not end.empty()) { if (not start.empty() and not end.empty()) { time = "from "+start+" to "+end; } else { if (end.empty()) { time = "since "+start; } else { time = "until "+end; } } } return time; } )>>


2
投票
assigned

12
投票
我认为我会消除条件,而需要一些数学运算:

const std::string fmtTime(const std::string& start, const std::string& end) { typedef std::string const &s; static const std::function<std::string(s, s)> f[] = { [](s a, s b) { return "from " + a + " to " + b; } [](s a, s b) { return "since " + a; }, [](s a, s b) { return "until " + b; }, [](s a, s b) { return ""; }, }; return f[start.empty() * 2 + end.empty()](start, end); }

编辑:如果愿意,可以将数学表示为start.empty() * 2 + end.empty()。要了解正在发生的事情,也许最好是我阐述一下我对事情的看法。我认为事物是2D数组:

“在此处输入图像描述”

((随意交换“开始为空”和“结束为空”,取决于您是想按行优先还是列优先顺序进行。]

start.empty()end.empty()(或它们的逻辑not,如果愿意的话)也分别沿着该2D矩阵的一维充当索引。所涉及的数学只是简单地“线性化”了该寻址,因此,我们得到的不是一行两行,而是一行,类似于:

<< img src =“ https://image.soinside.com/eyJ1cmwiOiAiaHR0cHM6Ly9pLnN0YWNrLmltZ3VyLmNvbS94U0RpcS5wbmcifQ==” alt =“在此处输入图像描述”>

用数学术语来说,这是“行*列+列”的简单问题(或者,反之亦然,取决于您是首选行优先还是列优先排序)。我最初将* 2部分表示为位移,并将加法表示为按位or(由于前一次左移,知道最低有效位为空)。我觉得这很容易处理,但我想我可以理解其他人可能不知道的地方。

我可能应该添加:尽管我已经提到行主列与列主列,但从两个“ x.empty”值到数组中位置的映射基本上是任意的,这一点应该很明显。从.empty()中获得的值表示不存在该值时将获得0,而当存在该值时将获得1。这样,从原始值到数组位置的直接映射可能像这样:

<< img src =“ https://image.soinside.com/eyJ1cmwiOiAiaHR0cHM6Ly9pLnN0YWNrLmltZ3VyLmNvbS9BNjBUQS5wbmcifQ==” alt =“在此处输入图像描述”>

由于我们将值线性化,因此我们对映射的方式有几种选择:

    简单地排列数组以适合我们获得的值。
  1. 分别反转每个维度的值(这基本上是导致原始问题的原因-不断使用!x.empty()
  2. 将两个输入合并为一个线性地址,然后通过减去3来“反转”。
  • 对于那些对此效率感到怀疑的人,它实际上可以编译为这个(使用VC ++):

    mov eax, ebx cmp QWORD PTR [rsi+16], rax sete al cmp QWORD PTR [rdi+16], 0 sete bl lea eax, DWORD PTR [rbx+rax*2] movsxd rcx, eax shl rcx, 5 add rcx, r14 mov r9, rdi mov r8, rsi mov rdx, rbp call <ridiculously long name>::operator()

    即使f的一次性构造也不像某些人想象的那么糟糕。它不涉及动态分配或该顺序上的任何内容。名称足够长,一开始看起来有点吓人,但最后,它主要是以下四个重复:

    lea rax, OFFSET FLAT:??_7?$_Func_impl@U?$_Callable_obj@V<lambda_f466b26476f0b59760fb8bb0cc43dfaf>@@$0A@@std@@V?$allocator@V?$_Func_class@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@AEBV12@AEBV12@@std@@@2@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@2@AEBV42@AEBV42@@std@@6B@ mov QWORD PTR f$[rsp], rax

    省略static const似乎并没有真正影响执行速度。由于表是静态的,所以我认为它应该存在,但是就执行速度而言,如果表初始化涉及四个单独的动态分配或类似的东西,这不是我们可以期望的巨大胜利。

  • 6
    投票
    您可以说

    if (theString.size()) { .... }

    是否更具可读性是另一回事。在这里,您正在调用一个方法的主要目的是不告诉您事物是否为空,并且依赖于隐式转换为bool。我希望使用!s.empty()版本。我可能会更喜欢使用not

    not

    [发现if (not theString.empty()) { .... }
    !版本令人困惑的人们之间的相关性可能很有趣。

    5
    投票
    我必须完全出于肛门保持性疾病而重构…

    not

    那里…干净干净。如果此处的内容难以阅读,请添加注释,而不要添加另一个std::string fmtTime( const std::string & start, const std::string & end ) {
        if ( start.empty() ) {
            if ( end.empty() ) return ""; // should diagnose an error here?
    
            return "until " + end;
        }
    
        if ( end.empty() ) return "since " + start;
    
        return "from " + start + " to " + end;
    }
    子句。

    4
    投票
    通常,最好不要使用如此复杂的条件代码。为什么不保持简单?

    if


    3
    投票
    全球范围内,您的编写方式没有问题;它的当然可以使别人更清楚提出。如果您担心 const std::string fmtTime(const std::string& start, const std::string& end) { if (start.empty() && end.empty()) { return ""; } // either start or end or both are not empty here. std::string time; if (start.empty()) { time = "until "+end; } else if (end.empty()) { time = "since "+start; } else // both are not empty { time = "from "+start+" to "+end; } return time; } 消失(是合理的担心),请使用更多的空白。

    !

    或尝试使用关键字if ( ! start.empty() || ! end.empty() ) ...
    代替:

    not

    ((对于大多数编辑者,if ( not start.empty() || not end.empty() ) ...
    将被突出显示为关键字,这将引起更多关注。)

    否则,有两个助手功能:

    not

    这具有赋予功能的附加优势一个更好的名字。 (函数名是动词,所以template <typename Container>
    bool
    isEmpty( Container const& container )
    {
        return container.empty();
    }
    
    template <typename Container>
    bool
    isNotEmpty( Container const& container )
    {
        return !container.empty();
    }
    逻辑上是指“清空容器”,而不是“是容器空”。但是,如果您开始将所有功能包装在标准库名称不佳,您的工作量减少了给你。)

    3
    投票
    不使用否定..)

    c.empty()

    编辑:好的清理了更多...

    3
    投票
    由于没有人愿意输入带有我的评论的完整答案,所以就这样:

    创建简化表达式的局部变量:

    const std::string fmtTime(const std::string& start, const std::string& end) { std::string ret; if (start.empty() == end.empty()) { ret = (start.empty()) ? "" : "from "+start+" to "+end; } else { ret = (start.empty()) ? "until "+end : "since "+start; } return ret; }

    编译器足够聪明,可以忽略那些变量,即使没有,它也不会比原始编译器有效(我希望两者都是对变量的单个测试)。对于只阅读条件的人来说,现在的代码有些[[可读:

    如果有开始或结束则

    当然,您也可能会进行不同的重构,以进一步简化嵌套操作的数量,例如在没有开始或结束的情况下将其选出并尽早解救...


    2
    投票
    对此的一种解决方案(当无法避免时,是检查显式条件,请考虑:

    std::string fmtTime(const std::string& start, const std::string& end) { std::string time; const bool hasStart = !start.empty(); const bool hasEnd = !end.empty(); if (hasStart || hasEnd) { if (hasStart && hasEnd) { time = "from "+start+" to "+end; } else { if (hasStart) { time = "since "+start; } else { time = "until "+end; } } } return time; }

    vs

    if (!container.empty())
    

    第二个版本更容易阅读,因为它的流动方式就像您大声读出来一样。这也清楚表明您正在检查错误条件。

    现在,如果这对于您仍然还不够好,我的建议是创建一个精简包装类,该包装类继承自您使用的任何容器,然后为该特定检查创建自己的方法。

    例如,使用字符串:

    if (container.empty() == false)

    现在变成正好:

    class MyString : public std::string
    {
       public:
         bool NotEmpty(void)
         { 
           return (empty() == false); 
         }
    };
    
    © www.soinside.com 2019 - 2024. All rights reserved.