protobuf之Printer

#protobuf #Printer

上节介绍了protobuf里的ZeroCopyStream,本文介绍下对应的辅助类Printer

作为C++的程序员,接触python后表示非常羡慕这样的字符串格式化:

person = {'name': 'bob', 'job': 'programer'}
print "My name is %(name)s, I am a %(job)s" % person

protobuf的作者实现了一个简单的类似功能,就是我们今天要介绍的Printer

1. Printer介绍

Printer主要应用在.pb.h .pb.cc文件的产出上,以官方example里的addressbook.proto为例,我们看下由protoc生成的addressbook.pb.h文件的前几行

// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: addressbook.proto

#ifndef PROTOBUF_addressbook_2eproto__INCLUDED
#define PROTOBUF_addressbook_2eproto__INCLUDED

#include <string>

#include <google/protobuf/stubs/common.h>

负责产出这段代码的代码则是google/protobuf/compiler/cpp/cpp_file.cc里的FileGenerator::GeneratorHelper(io::Printer* printer)

void FileGenerator::GenerateHeader(io::Printer* printer) {
  string filename_identifier = FilenameIdentifier(file_->name());

  // Generate top of header.
  printer->Print(
    "// Generated by the protocol buffer compiler.  DO NOT EDIT!\n"
    "// source: $filename$\n"
    "\n"
    "#ifndef PROTOBUF_$filename_identifier$__INCLUDED\n"
    "#define PROTOBUF_$filename_identifier$__INCLUDED\n"
    "\n"
    "#include <string>\n"
    "\n",
    "filename", file_->name(),
    "filename_identifier", filename_identifier);

对比下可以看到和addressbook.pb.h一一对应,格式化输出就通过Printer::Print完成。同时从用法上猜测$filename$ $filename_identifier这些是替换的源字符串。

接下来就介绍下Printer

Printer定义在google/protobuf/io/printer.h,namespace遵守目录的层级关系。类的实现短小精悍,接受一个ZeroCopyStream作为参数:

  // Create a printer that writes text to the given output stream.  Use the
  // given character as the delimiter for variables.
  Printer(ZeroCopyOutputStream* output, char variable_delimiter);

output用于输出,可以传入上篇文章介绍的各个ZeroCopyStream的子类,当然也可以是自己继承的子类。variable_delimiter是一个字符,用于包裹我们希望替换的源字符串,例如$filename$,格式化时通过检测$来查找需要替换的源字符串filename

对于如何提供字符串替换,支持两种形式:map 或者直接指定k-v

  // Print some text after applying variable substitutions.  If a particular
  // variable in the text is not defined, this will crash.  Variables to be
  // substituted are identified by their names surrounded by delimiter
  // characters (as given to the constructor).  The variable bindings are
  // defined by the given map.
  void Print(const map<string, string>& variables, const char* text);

  // Like the first Print(), except the substitutions are given as parameters.
  void Print(const char* text);
  // Like the first Print(), except the substitutions are given as parameters.
  void Print(const char* text, const char* variable, const string& value);
  // Like the first Print(), except the substitutions are given as parameters.
  void Print(const char* text, const char* variable1, const string& value1,
                               const char* variable2, const string& value2);

另外还支持Indent Outdent用于缩进,这个功能在生成代码时是必不可少的。注意默认生成的代码是两个空格吆

void Printer::Indent() {
  indent_ += "  ";
}

当然也支持PrintRaw输出源字符串。

接下来举个例子介绍下,前面例子里的代码也就明了了。

2. Printer例子

#include <map>
#include <string>
#include <iostream>

#include "google/protobuf/io/zero_copy_stream_impl.h"
#include "google/protobuf/io/printer.h"

int main() {
    //std::cout参数构造OstreamOutputStream
    google::protobuf::io::OstreamOutputStream ostream_output_stream(&std::cout);
    //$用于包裹待替换的源字符串
    google::protobuf::io::Printer printer(&ostream_output_stream, '$');
    //vars定义了替换的key:value
    std::map<std::string, std::string> vars;
    vars["name"] = "ufo";
    printer.Print(vars, "My name is $name$.\n");

    //运行时提示error,name这个key没有可替换的value
    printer.Print("My name is $name$.\n");
    //raw strings.
    printer.PrintRaw("My name is $name$.\n");

    const char* name_literal = "name";
    const std::string name = "ufo";
    //直接指定key:value的格式化方式
    printer.Print("My name is $name$.\n", name_literal, name);
    printer.Print("My name is $name$.\n", "name", "ufo");

    return 0;
}

例子里我们使用std::cout用于输出,因此构造了OstreamOutputStream,分别使用了Printer::print的map以及直接指定key:value的接口,注意这一句

    printer.Print("My name is $name$.\n");

因为没有传入vars,或者vars没有定义对应的key,则会报错[libprotobuf ERROR google/protobuf/io/printer.cc:97] Undefined variable: name

可以看到使用Printer,我们可以比较轻松的完成初始化的工作,在protoc工具生成h/cc文件时,大量的使用了该类。