上节介绍了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
文件时,大量的使用了该类。