paulo1205
(usa Ubuntu)
Enviado em 31/12/2022 - 15:44h
Normalmente você divide os arquivos da seguinte forma:
• a interface, que é o que o compilador precisa conhecer para que o programador possa usar a sua biblioteca sem produzir erros de compilação, vai no(s) arquivo(s) de cabeçalhos (tipicamente com sufixo “.h”), que permanece(m) em formato de código fonte e é (são) lido(s) a cada nova compilação de código que use sua biblioteca;
• a implementação, que é o que o
linker precisa conhecer para poder viabilizar a execução daquilo que a sua biblioteca se propõe a fazer, geralmente reside em um ou mais arquivos já compilados, quer como arquivos avulsos com código objeto (sufixo “.o” ou “.po” no mundo Unix, ou “.OBJ” no mundo Microsoft) quer em arquivos de bibliotecas estáticas (sufixo “.a” ou “.LIB”) ou dinâmicas (sufixo “.so” ou “.DLL”).
Um arquivo de cabeçalhos em C geralmente não provoca emissão de código objeto, porque contém apenas declarações de objetos (variáveis e funções) e de tipos incompletos, e definições de tipos (
struct s,
union s,
typedef s,
enum s), macros (i.e.
#define s) e constantes na forma de
labels de enumerações.
Em C++, cabeçalhos podem conter também outras definições de tipo (
class es,
enum class ,
enum struct ,
using ),
templates (mas não seu instanciamento),
concepts , e muitas pessoas admitem também definições de constantes de tipos nativos (por exemplo: “const int NUMBER=5;”) e funções
inline (explicitamente ou implicitamente, quando possível) porque, embora estas duas últimas possam provocar emissão de código, em C++ tais definições de constantes têm ligação interna, ao contrário do C, em que teriam ligação externa. No caso de
templates que possuam código C++ mais extenso, é comum (o GCC, por exemplo, faz isso) separar a parte mais declarativa, que permanece no arquivo com sufixo “.h”, da parte que contém os algoritmos dos
templates de funções ou funções-membros de classes, que é movida para arquivos com sufixo “.tcc”, fazendo com que o arquivo “.h” tenha um
#include do arquivo “.tcc”. Note, porém, que o arquivo “.tcc” não provoca imediatamente emissão de código objeto no momento da compilação, pois ele contém apenas a definição dos algoritmos de
templates ; a emissão de código correspondente somente ocorre quando o
template é instanciado.
A implementação daquilo que é descrito nos arquivos de cabeçalho geralmente reside em um ou mais arquivos com sufixos “.c” (para linguagem C) ou “.C”, “.cc”, “.cpp” ou “.cxx” (para C++), e incluem a definição de objetos (variáveis e funções), a definição de tipos incompletos e o possível instanciamento de
templates . Tais arquivos fontes são então compilados para o formato de código objeto, como acima mencionado, e são distribuídos diretamente nessa forma, ou, principalmente quando há múltiplos arquivos com código objeto, podem ser aglutinados na forma de bibliotecas estáticas e/ou dinâmicas (apenas um arquivo para cada versão).
Para exemplificar essa divisão, seguem alguns trechos de uma biblioteca que eu fiz para extração de dados a partir de uma fonte na rede (não posso publicar na íntegra por enquanto), ilustrando a divisão em “.h”, “.tcc” e “.cc”.
// Arquivo my_lib.h
#ifndef MY_LIB_H_included
#define MY_LIB_H_included
#include <string>
#include <stdexcept>
// Definição de tipo (classe) incluindo definição função-membro implicitamente inline (i.e. é inline mesmo sem ter a palavra-chave inline na declaração).
class my_exception: public std::runtime_error {
private:
int m_errcode;
public:
my_exception(int errcode);
my_exception(int errcode, const std::string &extra_info);
int errcode() const { return m_errcode; } // inline implícito (OK no cabeçalho).
};
/* ... */
// Algumas definições de tipos, incluindo alguns tipos com templates.
struct table_parse_error {
std::string raw_key, raw_data, message;
table_parse_error(const std::string &k, const std::string &d, const std::string &m);
bool operator <(const map_parse_error &other) const;
};
using parse_error_set=std::set<table_parse_error>; // “using” é uma alternativa a “typedef”.
// Tipos templated auxiliares para identificar se a extração está sendo feita para um container do tipo chave+valor (tal como std::map, std::unordered_map) ou que contém apenas chaves (tal como std::set),
// e também se a chave pode ocorrer apenas uma vez (tal como std::set, std::map, std::unordered_map) ou também chaves duplicadas (std::multiset, std::multimap etc.).
// Template de classe genérica para seleção do tipo correto de container (o caso geral supõe container chave+valor sem chaves duplicadas).
template <class CONT, bool IS_KEY_ONLY, bool IS_MULTI> struct tp_helper {
using K=typename CONT::key_type;
using mapped_type=typename CONT::mapped_type;
static bool default_inserter(CONT &st, const K &key, const mapped_type &val);
};
// Especialização parcial de tp_helper para containers chave+valor com duplicatas (e.g. std::multimap, std::unordered_multimap).
template <class CONT> struct tp_helper<CONT, false, true> {
using K=typename CONT::key_type;
using mapped_type=typename CONT::mapped_type;
static bool default_inserter(CONT &st, const K &key, const mapped_type &val);
};
// Especialização parcial de tp_helper para containers somente chave sem duplicatas (e.g. std::set, std::unordered_set).
template <class CONT> struct tp_helper<CONT, true, false> {
using K=typename CONT::key_type;
using mapped_type=std::string;
static bool default_inserter(CONT &st, const K &key, const mapped_type &val);
};
// Especialização parcial de tp_helper para containers somente chave com duplicatas (e.g. std::multiset).
template <class CONT> struct tp_helper<CONT, true, true> {
using K=typename CONT::key_type;
using mapped_type=std::string;
static bool default_inserter(CONT &st, const K &key, const mapped_type &val);
};
// Tipo para identificar se o container é somente valor: compara o tipo da chave com o tipo do valor armazenado.
template <class CONT> struct is_key_only {
static constexpr bool value=std::is_same<typename CONT::key_type, typename CONT::value_type>::value;
};
// Tipo para identificar se o container permite chaves duplicadas: vê se o tipo de retorno da função de inserção de valor é um iterador do container.
template <class CONT> struct is_multi_key {
static constexpr bool value=
std::is_same<
typename CONT::iterator,
decltype(CONT().insert(std::declval<typename CONT::value_type>()))
>::value
;
};
// Tipo para receber dados da tabela e popular um container.
template <class CONT> class table_parser {
public:
using K=typename CONT::key_type; // Tipo da chave.
using CONT_info=tp_helper<CONT, is_key_only<CONT>::value, is_multi_key<CONT>::value>; // Tipo informativo sobre o container (alias para uma instância de tp_helper para este container).
using V=typename CONT_info::mapped_type; // Tipo do valor (se for um container que só tem chave, usa std::string como dummy (ver acima), pois é o mesmo tipo usado durante o parsing da tabela).
protected:
S *p_storage;
/* ... */
public:
table_parser(CONT &storage);
/* ... */
/* etc. */
};
// Declaração de alguns templates de funções.
template<class CONT> void gettable(const table_parser<CONT> &extractor, const std::string &database, const std::string &table);
template<class K=std::string, class V=std::string> std::map<K, V> gettable(const std::string &database, const std::string &table);
template<class K=std::string> std::set<K> getkeys(const std::string &database, const std::string &table);
// Declaração de função comum.
std::string getvalue(const std::string &database, const std::string &nismap, const std::string &key);
// Note a inclusão do arquivo my_lib.tcc aqui. A separação foi feita para não poluir visualmente o arquivo my_lib.h,
// permitindo a quem o lê enxergue as declarações, sem se perder com os algoritmos convolutos dos templates.
#include "my_lib.tcc"
#endif // !defined(MY_LIB_H_included)
// Arquivo my_lib.tcc
// Algoritmo genérico (templated) para inseridor padrão não-especializado (containers chave+valor sem chaves duplicadas).
template <class CONT, bool IS_KEY_ONLY, bool IS_MULTI> bool tp_helper<CONT, IS_KEY_ONLY, IS_MULTI>::default_inserter(CONT &s, const K &k, const mapped_type &v){
auto result=s.emplace(k, v);
if(!result.second){
if(result.first==s.end())
return false;
result.first->second=v;
}
return true;
}
// Algoritmo genérico (templated) para inseridor padrão especializado para containers chave+valor com chaves duplicadas.
template <class CONT> bool tp_helper<CONT, false, true>::default_inserter(CONT &s, const K &k, const mapped_type &v){
auto result=s.emplace(k, v);
return result!=s.end();
}
// Algoritmo genérico (templated) para inseridor padrão especializado para containers somente-chave sem chaves duplicadas.
template <class S> bool tp_helper<S, true, false>::default_inserter(S &s, const K &k, const mapped_type &){
auto result=s.emplace(k);
return result.second || result.first!=s.end();
}
// Algoritmo genérico (templated) para inseridor padrão especializado para containers somente-chave com chaves duplicadas.
template <class S> bool tp_helper<S, true, true>::default_inserter(S &s, const K &k, const mapped_type &){
auto result=s.emplace(k);
return result!=s.end();
}
template<class CONT> void gettable(const table_parser<CONT> &extractor, const std::string &database, const std::string &table){
/* ... */
}
template<class K=std::string, class V=std::string> std::map<K, V> gettable(const std::string &database, const std::string &table){
using CONT=std::map<K, V>;
CONT result;
table_parser<CONT>(result);
/* ... */
return result;
}
template<class K=std::string> std::set<K> getkeys(const std::string &database, const std::string &table){
using CONT=std::set<K>;
CONT result;
table_parser<CONT>(result);
/* ... */
return result;
}
// Arquivo my_lib.cc
#include <string>
#include <cstring>
#include "my_lib.h" // Pega as declarações necessárias.
using namespace std;
// Implementação de funções-membos não-templated.
my_exception::my_exception(int errcode):
runtime_error(strerror(errcode)), m_errcode(errcode)
{
}
my_exception::my_exception(int errcode, const string &extra_info):
runtime_error(
((string(strerror(errcode))+=" [")+=extra_info)+=']'
), m_errcode(errcode)
{
}
/* ... */
string getvalue(const string &database, const string &nismap, const string &key){
char *db_data=nullptr;
size_t db_data_size=0;
/* ... */
if(!db_data)
throw my_exception(errno);
return string(db_data, db_data_size);
}
No Linux, a biblioteca estática se produz a partir o arquivo objeto.
g++ -Wall -Werror -pedantic-errors -O2 -std=c++11 -c my_lib.cc
ar rv libmy_lib.a my_lib.o
ranlib libmy_lib.a
Já a biblioteca dinânima pode ser gerada a partir de um objeto compilado com opção de gerar código relocável (
position independent code ).
g++ -Wall -Werror -pedantic-errors -O2 -std=c++11 -fPIC -c my_lib.cc -o my_lib.lo
g++ -shared -fPIC -o libmy_lib.so my_lib.lo
Para usar num programa, o código fonte em C++ precisa incluir o cabeçalho.
#include "my_lib.h"
E o executável gerado tem de fazer referência a uma das bibliotecas acima (por exemplo, usando a biblioteca estática).
g++ -Wall -Werror -pedantic-errors -O2 -std=c++11 -o test test.cc libmy_lib.a
... Então Jesus afirmou de novo: “(...) eu vim para que tenham vida, e a tenham plenamente.” (João 10:7-10)