【问题标题】:how to run multiple operator<< in one go如何一次性运行多个运算符<<
【发布时间】:2014-08-28 17:08:36
【问题描述】:

我已经实现了一个自定义记录器,我将信息转储到相应的文件:

像这样:

Logger::log["log_file"] << "Hi" << "Hi again" << "\n";

operator &lt;&lt; 的实现如下。请注意缓冲区达到限制时转储到流中的位置:

//This is the function signature of std::endl and some other manipulators
typedef CoutType& (*StandardEndLine)(CoutType&);
/// This method defines an operator<< to take in std::endl
BasicLogger& operator<<(StandardEndLine manip);
/// write the log items to buffer
template <typename T>
BasicLogger & operator<< (const T& val)
{
    std::stringstream *out = BasicLogger::getOut();
    *out << val;
    if(out->tellp() > 512000/*500KB*/){
        flushLog();
    }
    return *this;
}

我的问题在于有多个 &lt;&lt;s 的行:

Logger::log["log_file"] << "Hi" << "Hi again" << "\n";

在执行&lt;&lt; "Hi" 之后,缓冲区可能会变满并执行刷新到流。 这对我来说是不可取的。 如果一行还没有结束,我正在寻找一种解决方案来忽略该限制(500KB)。

示例场景: 当应用程序将由不同线程填充的缓冲区写入同一个文件时,这个问题会突出显示:第一个缓冲区的最后一行写了一半,当另一个线程的第二个缓冲区在我可以完成最后一行之前将另外 500kb 写入文件时第一个缓冲区。

感谢您的想法和解决方案。 谢谢

【问题讨论】:

  • 当你从多个线程写入同一个流和文件时,你需要锁定/同步。
  • 几乎与以下内容重复:stackoverflow.com/q/10110048/179910
  • 因此,如果在缓冲区中添加“Hi”会导致缓冲区已满,因此需要刷新,但您希望在行结束之前阻止这种情况,您建议在哪里存储额外的“ Hi again" 和 "\n" 字节,直到您决定刷新缓冲区是可以接受的?缓冲区已满,因此无法容纳更多数据...我想您可以重新设计缓冲区处理程序以在这种情况下调整缓冲区大小,但这不必要地复杂...
  • @AndrewMedico 拥有多个缓冲区的整个想法是尽可能避免锁定。事实上BasicLogger::getOut() 是一个map 的访问者,它的key 是thread_id :)
  • @twalberg 完全,我的意思是 my 缓冲区的限制是 500KB 已经结束。这意味着,缓冲区已被数百行其他行填满,并且在写入最后一行时,已达到限制(500KB)。我不介意为了完成最后一行而忽略此限制。

标签: c++ operator-keyword


【解决方案1】:

将多个&lt;&lt; 分组为单个刷新的传统解决方案是使用在语句末尾销毁的临时哨兵对象。

析构函数会刷新(如果需要),如果你想要的话,你甚至可以让它自动附加一个换行符。根据 Andrew Medico 的评论,如果您需要同步,哨兵 ctor/dtor 也应该锁定/解锁。

另一种方法是将所有内容收集到哨兵的本地 ostringstream 中,然后您只需从 dtor 锁定/写入/刷新/解锁单个字符串。

详情:

  1. 您的 Logger::log["log_file"] 运算符应返回一个临时对象,该对象具有您从上述选项中选择的行为
  2. 您的各种 &lt;&lt; 是操作员调​​用,将其作为第一个参数,并返回对您的哨兵类型的引用
  3. ;,临时哨兵超出范围并在其析构函数中执行任何刷新工作。

请注意,如果您只是委托给现有的 ostream,您可以轻松地拥有一个模板化的 Logger::sentry&amp; operator&lt;&lt; (Logger::sentry&amp;, T)


编辑:我以为我以前做过:问题不是重复的,但答案是重复的。 https://stackoverflow.com/a/19520409/212858

【讨论】:

  • 这似乎是一个非常好的解决方案。现在我仍然认为我不需要锁定。我试图想象Logger::sentry&amp; operator&lt;&lt; (Logger::sentry&amp;, T) 的示例用法会是什么样子?然后,如何实现它。我可以在某种程度上看到哨兵的析构函数是什么样子,但无法想象它是如何临时创建的,并且不会超出多个&gt;&gt;s 的范围。我想我需要你的帮助。你能举个例子吗?现在无需担心同步,因为out = getOut(); 将返回特定于当前线程的流。谢谢
【解决方案2】:

我正在寻找一种解决方案来忽略该限制(500KB)如果一行还没有结束

为什么不在刷新日志之前检查最后一个字符是 '\n' 呢?
如果我知道 T 中的内容,我会给你举个例子。如果它是一个 c++11 字符串,你可以这样做:

char ch = val.getString().back();
if(out->tellp() > 512000/*500KB*/  && ch == '\n'){
   flushLog();
}

【讨论】:

    【解决方案3】:

    只需标记一个回程车:

    bool ended = out.str().find('\n') != string::npos;
    if(out->tellp() > 512000/*500KB*/ && ended) {
    

    【讨论】:

      【解决方案4】:

      您可以更改flushLog 方法的实现。我假设您的实现将内部stringstream 的内容写入文件并清除它。 stringstream 的内容只能输出到最后一个换行符:

      void Whatever::flushLog()
      {
          std::stringstream& out = *BasicLogger::getOut();
          std::string stuff = out.str();
          size_t pos_of_newline = stuff.rfind('\n');
          if (pos_of_newline != std::string::npos)
          {
              std::string write_it_to_file = stuff.substr(0, pos_of_newline + 1);
              ... write it to file
              std::string leftover = stuff.substr(pos_of_newline + 1);
              out.str(leftover);
          }
      }
      

      代码只是一个想法;我没有检查它 - 也许有一些错误。此外:str() 调用复制了stringstream 的内容;你可能想改用它的rdbuf(我对rdbuf没有经验,所以不能推荐任何东西)。

      【讨论】:

      • 或者...,我们甚至可以在进入flushLog 之前进行此检查。这样我们就不会搞乱flushLog的疯狂实现了(疯狂:缓冲区之前是在堆上创建的,当它满了时,它的地址被推入一个无锁队列(在'flushLog) to be written to the corresponding file later by an IO thread and then delete the buffer(s). A new`中替换空缓冲区当完整的缓冲区被推送到队列时,缓冲区的完整。
      【解决方案5】:

      根据 Useless 的建议(由名为 Useless 的用户提出的建议!!!), 我使用Sentry(temporary) 对象解决了我的问题:

      1. Logger::log["log_file"] 将返回BasicLogger 的正确实例

      2. operator&lt;&lt; in BasicLogger 将返回 Sentry 对象的副本。

      3. 后续的&lt;&lt;s 将由operator&lt;&lt;Sentry 对象中处理
      4. Sentry 最终会在点击 ; 时超出范围,而其 destructor 将完成主要工作:
      class BasicLogger {
      /// operator overload. write the log items to buffer
        template<typename T>
        Sentry operator<<(const T& val) {
           Sentry t(*this,*getOut());
           t << val;//do the first << here
           return t;            
        }
      //...
        class Sentry {
            std::stringstream &out;
            BasicLogger &basicLogger;
        public:
            Sentry(BasicLogger & basicLogger_, std::stringstream &out_) :
                    out(out_), basicLogger(basicLogger_) {
            }
            Sentry(const Sentry& t) :
                    basicLogger(t.basicLogger), out(t.out) {
            }
            template<typename T>
      /// operator overload
            Sentry & operator<<(const T& val) {
                out << val;
                return *this;
            }
            ~Sentry() {
      // by some googling this estimated hardcode value promises less cycles to write to a file
                if (out.tellp() > 512000/*500KB*/) {
                    basicLogger.flushLog();
                }
            }
        };
      //...
      };
      
      class Logger {
      //...
      public:
        static Logger log;
        virtual BasicLogger & operator[](const std::string &key);
        virtual ~Logger();
      };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-04-06
        • 1970-01-01
        • 1970-01-01
        • 2017-02-14
        • 2013-01-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多