Desenvolvimento - C/C++
Usando Win32 API para otimizar o I/O, parte 2
Como eu já disse em um artigo anterior, estou fazendo um parser de arquivos IDL, usando somente C++ ISO (nada de Win32), STL e Boost. Apesar de estar realmente impressionado com o desempenho do meu parser do jeito que está, eu me propus a testar o nível de otimização de I/O que seria possível usando a API Win32 diretamente. Esta é a segunda parte do artigo.
por Rodrigo StraussAntes de começar a otimização do I/O do parser, é necessário fazer algumas considerações e algumas medições. A proposta inicial é otimizar o I/O do parser, mas será que vale mesmo a pena?
Fiz algumas medições para saber quanto tempo o parser demora para ler os arquivos que ele interpreta. Depois de várias medições (aproximadamente 50), cheguei a conclusão de que gastamos, em média, 20% do tempo com I/O. Em situações onde o computador acabou de ser ligado - ou seja, quando os arquivos não estão no Cache Manager do Windows - o tempo de acesso aos arquivos pode chegar a 50% do tempo gasto pelo parser. Não esqueça que só estamos medindo o tempo necessário para ler os arquivos (que como eu já disse, somam aproximadamente 400kb), não gravamos nada.
Tempo médio gasto com I/O: 20% Tempo máximo gasto com I/O: 50%
Já que a interpretação do arquivo em si leva 80% do tempo em média, não valeria mais a pena otimizá-la e de deixar o I/O para depois? Minha resposta é não, e vou explicar os motivos. A interpretação é o core do sistema, e o código que lida com isso foi feito da forma mais clara possível, para facilitar a manutenção e melhorias. E eu não estou disposto a sacrificar a clareza do código para que o parser fique mais rápido. Sendo assim, a otimização seria mais difícil, porque provavelmente envolveria mudanças em algoritmos, além de ser feita com essas requisição (clareza do código) em mente.
O motivo principal para otimizar o I/O é que, de acordo com o que vimos no post anterior, o I/O corresponde a menos de 4 linhas de código, enquando a classe de parser tem aproximadamente 850 linhas. Essas 4 linhas de código, algumas vezes, chegam a representar 50% do tempo gasto. Sendo assim, o custo-benefício da alteração me pareceu muito satisfatório, já que é fácil otimizar o I/O, afinal, ele é uma parte pontual e fácil de ser isolada e compreendida. A otimização do I/O não vai comprometer a legibilidade do código.
Conclusão: vale a pena otimizar o I/O. Depois dessas medições, começaremos a testar as otimizações possíveis e comprar os resultados.
Fontes do MidlParser
A função que dá início à interpretação do arquivo é a MidlParser::ParseMidlFile
CodeEntities.h
#pragma once struct IdlAttribute { IdlAttribute(const std::string& name, const std::string& value) : Name(name), Value(value) {} IdlAttribute(const std::string& name) : Name(name){} std::string Name; std::string Value; }; struct IdlParameter { IdlParameter(const std::string& type, const std::string& name) : Type(type), Name(name), IndirectionLevel(0) {} IdlParameter() : IndirectionLevel(0) {} typedef std::vector<IdlAttribute> AttributesContainer; typedef AttributesContainer::iterator AttributesIterator; typedef AttributesContainer::const_iterator ConstAttributesIterator; AttributesContainer Attributes; std::string Type; std::string Name; unsigned int IndirectionLevel; }; struct IdlMethod { IdlMethod(const std::string& name, const std::string& returnType) : Name(name), ReturnType(returnType) {} IdlMethod() {} typedef std::vector<IdlAttribute> AttributesContainer; typedef AttributesContainer::iterator AttributesIterator; typedef AttributesContainer::const_iterator ConstAttributesIterator; typedef std::vector<IdlParameter> ParametersContainer; typedef ParametersContainer::iterator ParametersIterator; typedef ParametersContainer::const_iterator ConstParametersIterator; AttributesContainer Attributes; std::string ReturnType; std::string Name; ParametersContainer Parameters; }; struct IdlInterface { IdlInterface(const std::string& name) : Name(name) {} IdlInterface() {} typedef std::vector<IdlAttribute> AttributesContainer; typedef AttributesContainer::iterator AttributesIterator; typedef AttributesContainer::const_iterator ConstAttributesIterator; typedef std::vector<IdlMethod> MethodsContainer; typedef MethodsContainer::iterator MethodsIterator; typedef MethodsContainer::const_iterator ConstMethodsIterator; AttributesContainer Attributes; std::string Name; std::string InheritsFrom; MethodsContainer Methods; }; // // functor // class IdlAttributeFindByName { private: std::string m_strName; // // Deve ser um shared_ptr pq um functor STL é passado como valor, e não como referência. // Se eu usar auto_ptr, na primeira cópia o ownership do ponteiro vao para a cópia e // ficamos sem nada... // boost::shared_ptr<std::vector<std::string> > m_MoreAttributesToFind; public: IdlAttributeFindByName(const std::string& name) : m_strName(name) {} IdlAttributeFindByName() {} void AddAttributeToFind(const std::string& str) { if(m_MoreAttributesToFind.get() == NULL) m_MoreAttributesToFind = boost::shared_ptr<std::vector<std::string> >(new std::vector<std::string>()); m_MoreAttributesToFind->push_back(str); } bool operator()(const IdlAttribute& att) { if(m_MoreAttributesToFind.get() == NULL) return att.Name == m_strName; else { return att.Name == m_strName || std::find(m_MoreAttributesToFind->begin(), m_MoreAttributesToFind->end(), att.Name) != m_MoreAttributesToFind->end(); } } };
MidlParser.h
#pragma once // // MidlParser // // Faz o parser de um arquivo IDL // class MidlParser { public: class ParseException : public std::exception { private: unsigned int m_line; string m_fileName; public: ParseException(const string& what, MidlParser& parser, const string& fileName) : std::exception(what.c_str()), m_line(parser.GetCurrentLine()), m_fileName(fileName) { } void set_line(unsigned int line) { m_line = line; } unsigned int get_line() const { return m_line; } void set_fileName(const string& fileName) { m_fileName = fileName; } const string& get_fileName() const { return m_fileName; } }; private: bool m_bParseAsImportFile; string m_parsedFileName; typedef tokenizer<char_separator<char> > TokenizerMidl; vector<string> m_KnownInterfaces; vector<string> m_KnownTypes; map<string, IdlInterface> m_ParsedInterfaces; vector<string> m_IncludePaths; vector<string> m_ParsedIncludeFiles; TokenizerMidl m_Tokenizer; TokenizerMidl::iterator m_TokenIterator; std::string m_strInvalidToken; TokenizerMidl::iterator m_LineCountIterator; mutable unsigned int m_uiCurrentLine; #ifdef _DEBUG // // isso facilita a visualização em debug // std::string m_strCurrentToken; const char* m_szCurrentToken; unsigned int m_uiBreakOnLine; #endif void Log(const string& msg); // // verifica se é um identificador válido // const string& Identifier(const string& token); const string& NextTokenAsIdentifier(); const string& CurrentTokenAsIdentifier(); void CheckIsExpectedToken(const string& str, const string& checked, const char* expected_name = NULL); void CheckExpectedNextToken(const std::string expected, const char* expected_name = NULL); void CheckExpectedCurrentToken(const std::string expected, const char* expected_name = NULL); void CheckNotExpectedToken(const string& str, const string& checked, const char* not_expected_name = NULL); void CheckNotExpectedNextToken(const std::string not_expected, const char* not_expected_name = NULL); void CheckNotExpectedCurrentToken(const std::string not_expected, const char* not_expected_name = NULL); void CheckType(const string& str, unsigned int indirectionLevel, bool mustBeInterface = false); const string& NextTokenAsType(unsigned int indirectionLevel = 0, bool mustBeInterface = false); const string& CurrentTokenAsType(unsigned int indirectionLevel = 0, bool mustBeInterface = false); void AddParsedInterface(const IdlInterface& iface); void AddKnownInterfaceName(const string& interfaceName); unsigned int GetCurrentLine(); const std::string& NextToken(bool checkEndOfFile = true, bool skipComments = true); const std::string& CurrentToken(bool checkEndOfFile = true, bool skipComments = true); void SkipUntilToken(const string& token); void SkipQuote(); void SkipUntilClose(const string& open_token, const string& close_token); void LoadKnownInterfaces(); void LoadKnownTypes(); void ParseMethod(IdlMethod* method); void ParseInterface(IdlInterface* iface); void ParseAttributes(vector<IdlAttribute>* attributes); void ParseImportFile(const string& fileName); bool IsEndOfFile(); bool OpenIncludeFile(const string& fileName, fstream* f, string* fileNameWithPath); public: MidlParser(); void AddFindPath(const string& path); void ParseMidlFile(const char* fileName); const std::map<string, IdlInterface>& GetParsedInterfaces() const; const std::vector<string>& GetKnownInterfaces() const; void Dump(ostream& s) const; private: void AddKnownType(const string& t); };
MidlParser.cpp
#pragma once #include "stdafx.h" #include "CodeEntities.h" #include "MidlParser.h" // // verifica se é um identificador válido // const string& MidlParser::Identifier(const string& token) { bool isValid = true; if(token.size() == 0 || (!std::isalpha(token[0]) && token[0] != "_")) isValid = false; else { for(string::const_iterator i = token.begin() ; i != token.end() ; ++i) { char c = *i; if(!std::isalpha(c) && !std::isalnum(c) && c != "_") { isValid = false; break; } } } if(!isValid) throw ParseException("invalid identifier: "" + token + """, *this, m_parsedFileName); return token; } const string& MidlParser::NextTokenAsIdentifier() { return Identifier(NextToken()); } const string& MidlParser::CurrentTokenAsIdentifier() { return Identifier(CurrentToken()); } void MidlParser::CheckIsExpectedToken(const string& str, const string& checked, const char* expected_name) { if(str != checked) { string ex("expected: "); if(expected_name) ex += expected_name; else { ex += """; ex += checked; ex += """; } throw ParseException(ex, *this, m_parsedFileName); } } void MidlParser::CheckExpectedNextToken(const std::string expected, const char* expected_name) { CheckIsExpectedToken(NextToken(), expected, expected_name); } void MidlParser::CheckExpectedCurrentToken(const std::string expected, const char* expected_name) { CheckIsExpectedToken(CurrentToken(), expected, expected_name); } void MidlParser::CheckNotExpectedToken(const string& str, const string& checked, const char* not_expected_name) { if(str == checked) { string ex("not expected: "); if(not_expected_name) ex += not_expected_name; else { ex += """; ex += checked; ex += """; } throw ParseException(ex, *this, m_parsedFileName); } } void MidlParser::CheckNotExpectedNextToken(const std::string not_expected, const char* not_expected_name) { CheckNotExpectedToken(NextToken(), not_expected, not_expected_name); } void MidlParser::CheckNotExpectedCurrentToken(const std::string not_expected, const char* not_expected_name ) { CheckNotExpectedToken(CurrentToken(), not_expected, not_expected_name); } void MidlParser::CheckType(const string& str, unsigned int indirectionLevel, bool mustBeInterface ) { if(std::find(m_KnownInterfaces.begin(), m_KnownInterfaces.end(), str) == m_KnownInterfaces.end()) { if(mustBeInterface == true) throw ParseException("Unknwon Type: "" + str + """, *this, m_parsedFileName); } else return; if(std::find(m_KnownTypes.begin(), m_KnownTypes.end(), str) == m_KnownTypes.end()) throw ParseException("Unknwon Type: "" + str + """, *this, m_parsedFileName); } const string& MidlParser::NextTokenAsType(unsigned int indirectionLevel, bool mustBeInterface) { CheckType(NextToken(), indirectionLevel, mustBeInterface); return CurrentToken(); } const string& MidlParser::CurrentTokenAsType(unsigned int indirectionLevel, bool mustBeInterface) { CheckType(CurrentToken(), indirectionLevel, mustBeInterface); return CurrentToken(); } void MidlParser::AddKnownInterfaceName(const string& interfaceName) { if(std::find(m_KnownInterfaces.begin(), m_KnownInterfaces.end(), interfaceName) == m_KnownInterfaces.end()) m_KnownInterfaces.push_back(interfaceName); } void MidlParser::AddParsedInterface(const IdlInterface& iface) { m_ParsedInterfaces.insert(make_pair<string, IdlInterface>(iface.Name, iface)); m_KnownInterfaces.push_back(iface.Name); AddKnownType(iface.Name); } unsigned int MidlParser::GetCurrentLine() { for( ; m_LineCountIterator != m_TokenIterator ; ++m_LineCountIterator) if(*m_LineCountIterator == "n") m_uiCurrentLine++; return m_uiCurrentLine; } const std::string& MidlParser::NextToken(bool checkEndOfFile, bool skipComments) { ++m_TokenIterator; return CurrentToken(checkEndOfFile, skipComments); } const std::string& MidlParser::CurrentToken(bool checkEndOfFile, bool skipComments) { if(IsEndOfFile()) { if(checkEndOfFile) throw ParseException("unexpected end of file", *this, m_parsedFileName); else return m_strInvalidToken; } if(skipComments) { for(;;) { if(*m_TokenIterator != "/" && *m_TokenIterator != "n") break; // // pula os comentários // while(*m_TokenIterator == "/") { TokenizerMidl::iterator next = m_TokenIterator; next++; if(*next == "/") { while(m_TokenIterator != m_Tokenizer.end() && *++m_TokenIterator != "n"); break; } else if(*next == "*") { ++m_TokenIterator; for(;;) { ++m_TokenIterator; if(IsEndOfFile()) { if(checkEndOfFile) throw ParseException("unexpected end of file", *this, m_parsedFileName); else return m_strInvalidToken; } const string& token = *m_TokenIterator; if(token == "*") { ++m_TokenIterator; if(IsEndOfFile()) { if(checkEndOfFile) throw ParseException("unexpected end of file", *this, m_parsedFileName); else return m_strInvalidToken; } if(*m_TokenIterator == "/") { ++m_TokenIterator; break; } } } } else break; } // // pula os n // while(++m_TokenIterator != m_Tokenizer.end() && *m_TokenIterator == "n"); if(IsEndOfFile()) { if(checkEndOfFile) throw ParseException("unexpected end of file", *this, m_parsedFileName); else return m_strInvalidToken; } } } #ifdef _DEBUG // // isso facilita a visualização em debug // m_strCurrentToken = *m_TokenIterator; m_szCurrentToken = m_strCurrentToken.c_str(); if(GetCurrentLine() >= m_uiBreakOnLine) { m_uiBreakOnLine = 0xFFFFFFFF; __asm int 3; } #endif return *m_TokenIterator; } void MidlParser::SkipUntilToken(const string& token) { std::find(m_TokenIterator, m_Tokenizer.end(), token); } void MidlParser::SkipQuote() { bool stringOpened = false; for(;;) { const string& token = NextToken(true, false); if(token == """) { if(stringOpened) return; else { stringOpened = true; continue; } } // // pular escape de aspa // if(token == "" && NextToken(true, false) == """) NextToken(true, false); } } void MidlParser::SkipUntilClose(const string& open_token, const string& close_token) { unsigned int deep = 0; for(;;) { if(CurrentToken() == open_token) { deep++; } else if(CurrentToken() == close_token) { deep--; if(deep == 0) break; } NextToken(); } } void MidlParser::ParseMethod(IdlMethod* method) { if(CurrentToken() == "[") ParseAttributes(&method->Attributes); method->ReturnType = CurrentTokenAsIdentifier(); method->Name = NextTokenAsIdentifier(); CheckExpectedNextToken("("); if(NextToken() != ")") { for(unsigned int uiCurrentParameter = 0 ; ; ++uiCurrentParameter) { IdlParameter param; // // vamos ver se é atributos // if(CurrentToken() == "[") { ParseAttributes(¶m.Attributes); } param.Type = CurrentTokenAsIdentifier(); while(NextToken() == "*") param.IndirectionLevel++; // // tratar sintaxe Metodo(void); // if(param.Type == "void" && CurrentToken() == ")") { break; } CheckType(param.Type, param.IndirectionLevel); param.Name = CurrentTokenAsIdentifier(); method->Parameters.push_back(param); if(NextToken() == ")") { break; } if(CurrentToken() == ",") { NextToken(); continue; } throw ParseException("expected: "," or ")"", *this, m_parsedFileName); } } NextToken(); CheckExpectedCurrentToken(";"); NextToken(); } void MidlParser::ParseInterface(IdlInterface* iface) { assert(CurrentToken() == "interface"); iface->Name = NextTokenAsIdentifier(); // // herança de interface // if(NextToken() == ":") { CheckNotExpectedNextToken("{", "interface name"); iface->InheritsFrom = CurrentTokenAsType(); CheckType(iface->InheritsFrom, 0, true); NextToken(); } CheckExpectedCurrentToken("{"); NextToken(); // // temos que colocar a própria interface na lista de tipos // conhecidos, já que um método pode receber um ponteiro // do próprio tipo // AddKnownType(iface->Name); // // métodos // if(CurrentToken() != "}") { for(;;) { IdlMethod method; ParseMethod(&method); iface->Methods.push_back(method); // // fim da interface // if(CurrentToken() == "}") break; } } CheckExpectedNextToken(";"); NextToken(); } void MidlParser::LoadKnownInterfaces() { // // essa maravilha de sintaxe é graças ao boost::assign // m_KnownInterfaces.clear(); m_KnownInterfaces += "IUnknown", "IDispatch"; return; } void MidlParser::LoadKnownTypes() { // // por enquanto vou colocar os defines tb, para facilitar. Quando // isso tiver um pré-processador isso deve ser tirado // m_KnownTypes.clear(); m_KnownTypes += "BSTR", "VARIANT", "DWORD", "LONG", "ULONG", "GUID", "REFGUID", "HRESULT", "BOOL", "LPVOID", "REFCLSID", "WCHAR", "REFIID", "VOID", "void", "VARIANT_BOOL", "USHORT", "int", "short", "UCHAR"; } void MidlParser::ParseAttributes(vector<IdlAttribute>* attributes) { std::stringstream buffer; assert(CurrentToken() == "["); for(;;) { if(CurrentToken() == "]") break; IdlAttribute att(NextTokenAsIdentifier()); // // se for vírgula, acabou esse // if(NextToken() == ",") { attributes->push_back(att); continue; } // // se abre parenteses, tem valor // if(CurrentToken() == "(") { if(NextToken() == ")") throw ParseException((boost::format("Attribute %1% has null value") % CurrentToken()).str(), *this, m_parsedFileName); // // vamos encontrar o fim do parênteses // buffer.clear(); while(CurrentToken() != ")") { buffer << CurrentToken(); NextToken(); } NextToken(); att.Value = buffer.str(); } attributes->push_back(att); } NextToken(); } void MidlParser::Log(const string& msg) { cerr << msg << endl; } bool MidlParser::IsEndOfFile() { return m_TokenIterator == m_Tokenizer.end(); } bool MidlParser::OpenIncludeFile(const string& fileName, fstream* f, string* fileNameWithPath) { for(vector<string>::const_iterator i = m_IncludePaths.begin() ; i != m_IncludePaths.end() ; ++i) { string str; f->close(); f->clear(); str = *i + fileName; f->open(str.c_str(), ios::in); if(f->good()) { if(fileNameWithPath) *fileNameWithPath = str; return true; } } return true; } void MidlParser::ParseImportFile(const string& fileName) { fstream f; string str; // // deve ter extensão IDL e não ter sido interpretado ainda // if(fileName.length() < 5 || fileName.substr(fileName.size() - 4, 4) != ".idl" || std::find(m_ParsedIncludeFiles.begin(), m_ParsedIncludeFiles.end(), fileName) != m_ParsedIncludeFiles.end()) return; MidlParser ImportParser; ImportParser.m_bParseAsImportFile = true; ImportParser.m_KnownTypes = m_KnownTypes; ImportParser.m_KnownInterfaces = m_KnownInterfaces; ImportParser.m_ParsedIncludeFiles = m_ParsedIncludeFiles; ImportParser.ParseMidlFile(fileName.c_str()); m_KnownTypes = ImportParser.m_KnownTypes; m_KnownInterfaces = ImportParser.m_KnownInterfaces; m_ParsedIncludeFiles = ImportParser.m_ParsedIncludeFiles; } // // // PUBLIC // // MidlParser::MidlParser() : m_Tokenizer(std::string()), m_bParseAsImportFile(false) { #ifdef _DEBUG m_uiBreakOnLine = 0xFFFFFFFF; #endif AddFindPath("C:Program FilesMicrosoft SDKinclude"); } void MidlParser::ParseMidlFile(const char* fileName) { fstream f; string str; std::vector<IdlAttribute> attributes; if(m_bParseAsImportFile) { if(!OpenIncludeFile(fileName, &f, &m_parsedFileName)) throw ParseException(string("error opening import file "") + fileName + """, *this, m_parsedFileName); getline(f, str, f.widen(EOF)); m_Tokenizer.assign(str,char_separator<char>("\t\r " , "\n\"*,;:{}/[]()")); m_ParsedIncludeFiles.push_back(fileName); Log(string("Parsing import file ") + fileName); } else { f.open(fileName, ios::in); if(f.fail()) throw ParseException(string("error opening file "") + fileName + """, *this, m_parsedFileName); getline(f, str, f.widen(EOF)); m_Tokenizer.assign(str,char_separator<char>("\t\r " , "\n\"*,;:{}/[]()")); m_parsedFileName = fileName; LoadKnownInterfaces(); LoadKnownTypes(); } m_TokenIterator = m_Tokenizer.begin(); m_LineCountIterator = m_TokenIterator; m_uiCurrentLine = 1; for(;;) { const string token = CurrentToken(false); if(IsEndOfFile()) break; // // início de atributos // if(token == "[") { if(m_bParseAsImportFile) { SkipUntilClose("[", "]"); continue; } attributes.clear(); ParseAttributes(&attributes); // // se não for atributo de uma interface vamos ignorar. // Acho melhor do que verificar antes se é uma interface e ter que "rebobinar" // o iterator (o que, na realidade, não é possível pq o iterator é forward-only) // if(CurrentToken() != "interface") attributes.clear(); } else if(token == "cpp_quote") { SkipQuote(); CheckExpectedNextToken(")"); } else if(token == "coclass") { SkipUntilClose("{", "}"); NextToken(); } else if(token == "import") { stringstream stm; CheckExpectedNextToken("""); while(NextToken(true, false) != """) stm << CurrentToken(true, false); ParseImportFile(stm.str()); CheckExpectedNextToken(";"); NextToken(); } else if(token == "#define") { for(;;) { if(NextToken(true, false) == "n") break; // // se for vamos pular um token, pq se o próximo // for um n ele será pulado. Senão não tem efeito mesmo... // if(CurrentToken(true, false) == "") NextToken(true, false); } } else if(token == "interface") { if(m_bParseAsImportFile) { AddKnownInterfaceName(NextTokenAsIdentifier()); if(NextToken() != ";") SkipUntilClose("{", "}"); continue; } IdlInterface iface; if(attributes.empty()) { // // se não tem atributos TEM QUE SER um forward declaration // string name; name = NextTokenAsIdentifier(); CheckExpectedNextToken(";"); NextToken(); m_KnownInterfaces.push_back(name); continue; } // // coloca os atributos encontrados na interface // iface.Attributes = attributes; attributes.clear(); ParseInterface(&iface); AddParsedInterface(iface); } else if(token == "enum") { // // por enquanto só vamos pegar o nome do enum para considerarmos // como um tipo válido // SkipUntilClose("{", "}"); if(NextToken() != ";") { AddKnownType(CurrentTokenAsIdentifier()); CheckExpectedNextToken(";"); } NextToken(); } else { NextToken(false); } } if(!m_bParseAsImportFile) { cout << "Finish (" << g_dwIoTime << " / " << dwTickCount << " / " << std::fixed << percent << ")" << endl; } return; } void MidlParser::AddFindPath(const string& path) { if(std::find(m_IncludePaths.begin(), m_IncludePaths.end(), path) == m_IncludePaths.end()) { if(*path.rbegin() != "") m_IncludePaths.push_back(path + ""); else m_IncludePaths.push_back(path); } } const std::map<string,IdlInterface>& MidlParser::GetParsedInterfaces() const { return m_ParsedInterfaces; } const std::vector<string>& MidlParser::GetKnownInterfaces() const { return m_KnownInterfaces; } // -------------------------------------------------------------------------- // select1st, select2nd // -------------------------------------------------------------------------- #include <boost/call_traits.hpp> template <class Pair> struct select1st : std::unary_function<Pair,typename Pair::first_type> { typename const Pair::first_type& operator()(typename boost::call_traits<Pair>::param_type x) const { return x.first; } }; template <class Pair> struct select2nd : std::unary_function<Pair,typename Pair::second_type> { typename const Pair::second_type& operator()(typename boost::call_traits<Pair>::param_type x) const { return x.second; } }; void MidlParser::Dump(ostream& s) const { s << "KnownInterfaces" << endl << string(30, "=") << endl; std::copy(m_KnownInterfaces.begin(), m_KnownInterfaces.end(), std::ostream_iterator<string>(s, "rn")); s << endl << "ParsedInterfaces" << endl << string(30, "=") << endl; std::copy( boost::make_transform_iterator(m_ParsedInterfaces.begin(), select1st<const std::map<string, IdlInterface>::value_type>()), boost::make_transform_iterator(m_ParsedInterfaces.end(), select1st<const std::map<string, IdlInterface>::value_type>()), std::ostream_iterator<string>(s, "rn")); s.flush(); } void MidlParser::AddKnownType(const string& t) { if(std::find(m_KnownTypes.begin(), m_KnownTypes.end(), t) == m_KnownTypes.end()) { m_KnownTypes.push_back(t); } }