将手写循环转换为std库调用

Mar*_*arc 5 c++ c++11 c++14

我最近看过Sean Parent关于2013年C++调味的讲话.如果我理解他,他说你可以从代码中消除几乎所有(全部?)手写循环.我的问题是如何实现这一目标?我们考虑以下代码:

class ProgressDialog
{
  //interesting part of that class
  void SetPosition(int position);
  bool IsCancelRequested();
  void SetHeader(const std::string& status);
}
void foo( const std::vector<std::string>& v)
{
  ProgressDialog dlg;
  long position = 0;
  for( const auto& s : v)
  {
    ++position;
    dlg.SetPosition(position);
    dlg.SetHeader("Processing"+ s);
    DoSomethingThatTakesSomeTime(s);
    if(dlg.IsCancelRequested()) break;
  }
}
Run Code Online (Sandbox Code Playgroud)

有没有办法重构手写循环?

Ric*_*ges 6

我不确定这是否会增加任何清晰度,但它试图重构循环和早期休息的概念.

#include <string>
#include <vector>
#include <ciso646>

struct ProgressDialog
{
  //interesting part of that class
  void SetPosition(int position);
  bool IsCancelRequested();
  void SetHeader(const std::string& status);
};
void DoSomethingThatTakesSomeTime(std::string const&);

// iterate over a container, calling func with the current value.
// if func returns false, cease iterating immediately.
// return true if terminated early
// note: func must return false if it wishes early termination,
// otherwise true
template<class Cont, class F> 
auto for_each_while(Cont&& container, F&& func)
{
    for(auto&& s : container)
        if (not func(s)) 
            return true;
    return false;
}

void foo( const std::vector<std::string>& v)
{
    auto update_dialog = 
    [position = 0, dlg = ProgressDialog()](auto&& s) mutable
    {
        ++position;
        dlg.SetPosition(position);
        dlg.SetHeader("Processing"+ s);
        DoSomethingThatTakesSomeTime(s);
        return !dlg.IsCancelRequested();
    };
    for_each_while(v, update_dialog);
}
Run Code Online (Sandbox Code Playgroud)

这里有一些std库滥用实现了同样的事情.

我强烈建议你不要这样做,因为随便的读者不清楚发生了什么!

void foo( const std::vector<std::string>& v)
{
    int position = 0;
    auto dlg = ProgressDialog();

    std::find_if(begin(v), end(v), 
                 [&](auto&& s)
    {
        ++position;
        dlg.SetPosition(position);
        dlg.SetHeader("Processing"+ s);
        DoSomethingThatTakesSomeTime(s);
        return dlg.IsCancelRequested();
    });
}
Run Code Online (Sandbox Code Playgroud)

  • @ zett42 ^^这个.但正如答案中所提到的,我怀疑抽象是否确实增加了任何清晰度. (2认同)

ein*_*ica 5

一般情况

如果我理解他是正确的,他说你可以从代码中消除几乎所有(所有?)手写循环.

嗯,是的,不是 - 显然这取决于你写的循环类型.我认为,他略微夸张地强调了许多循环并非真正必要的事实,并且可能更好地重构为标准库"算法"的应用.

此外,基本上任何循环都可以用a替换std::for_each,但实际上并不重要,因为它只隐藏了显式的循环进程控制,并且仍然只是"循环".

你的具体例子

在您的情况下,循环迭代确实有效,同时记录/报告正在完成的工作的每次迭代,并且愿意接受中止请求.您可以考虑编写std::for_each具有该功能的其他方面的变体,然后将其用作实际工作作为特殊情况,例如

namespace with_progress_dialog {

template< class InputIt, class UnaryFunction >
void for_each(
    InputIt          first, 
    InputIt          last,
    UnaryFunction    function,
    std::string_view message_prefix = "Processing item " )
{
    ProgressDialog progress_dialog;
    for (position_t position = 0; first != last; ++first, ++position) 
    {
        progress_dialog.SetPosition(position);
        progress_dialog.SetHeader(message_prefix + position);
        function(*first);
        if ( progress_dialog.IsCancelRequested() ) { break; }
    }
}
} 
Run Code Online (Sandbox Code Playgroud)

然后打电话

// ... etc. etc. ...
with_progress_dialog::for_each(
    std::begin(v), std::end(v),
    &DoSomethingThatTakesSomeTime);
Run Code Online (Sandbox Code Playgroud)

现在,这肯定是一种过度概括.但我假设你有其他情况,你打开一个逐步更新的进度对话框.所以也许相应地调整你的概括.或者 - 也许你可以在进行繁重的工作时保持某种窗口级状态,并让另一个线程跟踪并在必要时打开一个对话窗口,或者在状态栏上显示一个指示.还有另一个选择可能是让对话在共同运行中运行(但这很投机,不确定它是个好主意).


注意:继续打印正在处理的字符串并不是一个好主意,也可能是不安全的 - 这可能源于您无法控制的输入.你需要确保它们不会太长,并考虑对它们进行消毒.在我的代码中,我只打印索引("位置").