Arquivos de cabeçalho (C++)
Os nomes dos elementos do programa, como variáveis, funções, classes etc., devem ser declarados para serem usados. Por exemplo, você não pode simplesmente escrever x = 42
sem primeiro declarar 'x'.
int x; // declaration
x = 42; // use x
A declaração informa ao compilador se o elemento é um int
, um double
, uma função, uma class
ou alguma outra coisa. Além disso, cada nome deve ser declarado (direta ou indiretamente) em cada arquivo .cpp no qual ele é usado. Quando você compila um programa, cada arquivo .cpp é compilado de maneira independente em uma unidade de compilação. O compilador não tem conhecimento de quais nomes são declarados em outras unidades de compilação. Isso significa que, se você definir uma classe ou função ou variável global, deverá fornecer uma declaração dela em cada arquivo .cpp adicional que a usa. Cada declaração dela deve ser exatamente idêntica em todos os arquivos. Uma pequena inconsistência causará erros ou comportamento não intencional quando o vinculador tentar mesclar todas as unidades de compilação em um só programa.
Para minimizar o potencial de erros, o C++ adotou a convenção de usar arquivos de cabeçalho para conter declarações. Você faz as declarações em um arquivo de cabeçalho e depois usa a diretiva #include em cada arquivo .cpp ou outro arquivo de cabeçalho que requer essa declaração. A diretiva #include insere uma cópia do arquivo de cabeçalho diretamente no arquivo .cpp antes da compilação.
Observação
No Visual Studio 2019, o recurso módulos C++20 é introduzido como uma melhoria e eventual substituição para arquivos de cabeçalho. Para obter mais informações, confira Visão Geral de módulos no C++.
Exemplo
O exemplo a seguir mostra um modo comum de declarar uma classe e usá-la em um arquivo de origem diferente. Vamos começar com o arquivo de cabeçalho, my_class.h
. Ele contém uma definição de classe, mas observe que a definição está incompleta; a função de membro do_something
não está definida:
// my_class.h
namespace N
{
class my_class
{
public:
void do_something();
};
}
Então crie um arquivo de implementação (normalmente com uma extensão .cpp ou semelhante). Chamaremos o arquivo my_class.cpp e forneceremos uma definição para a declaração de membro. Adicionamos uma diretiva #include
para o arquivo "my_class.h" para que a declaração my_class seja inserida neste ponto no arquivo .cpp e incluímos <iostream>
para efetuar pull da declaração para std::cout
. Observe que as aspas são usadas para arquivos de cabeçalho no mesmo diretório que o arquivo de origem e colchetes angulares são usados para cabeçalhos de biblioteca padrão. Além disso, muitos cabeçalhos de biblioteca padrão não têm .h nem nenhuma outra extensão de arquivo.
No arquivo de implementação, opcionalmente, podemos usar uma instrução using
para evitar a qualificação de todas as menções de "my_class" ou "cout" com "N::" ou "std::". Não coloque instruções using
em seus arquivos de cabeçalho.
// my_class.cpp
#include "my_class.h" // header in local directory
#include <iostream> // header in standard library
using namespace N;
using namespace std;
void my_class::do_something()
{
cout << "Doing something!" << endl;
}
Agora podemos usar my_class
em outro arquivo .cpp. Nós incluímos (#include) o arquivo de cabeçalho para que o compilador efetue pull na declaração. Tudo o que o compilador precisa saber é que my_class é uma classe que tem uma função de membro público chamada do_something()
.
// my_program.cpp
#include "my_class.h"
using namespace N;
int main()
{
my_class mc;
mc.do_something();
return 0;
}
Depois que o compilador terminar de compilar cada arquivo .cpp em arquivos .obj, ele passará os arquivos .obj para o vinculador. Quando o vinculador mescla os arquivos de objeto, ele encontra exatamente uma definição para my_class; ela está no arquivo .obj produzido para my_class.cpp e o build é bem-sucedido.
Incluir guardas
Normalmente, os arquivos de cabeçalho têm um proteção de inclusão ou uma diretiva #pragma once
para garantir que eles não sejam inseridos várias vezes em um só arquivo .cpp.
// my_class.h
#ifndef MY_CLASS_H // include guard
#define MY_CLASS_H
namespace N
{
class my_class
{
public:
void do_something();
};
}
#endif /* MY_CLASS_H */
O que colocar em um arquivo de cabeçalho
Como um arquivo de cabeçalho pode ser incluído por vários arquivos, ele não pode conter definições que possam produzir várias definições de mesmo nome. Os seguintes não são permitidos ou são considerados uma prática muito ruim:
- definições de tipo internas no namespace ou no escopo global
- definições de função não embutidas
- definições de variáveis não const
- definições de agregação
- namespaces sem nome
- Diretivas using
O uso da diretiva using
não necessariamente causará um erro, mas pode causar um problema porque ele coloca o namespace no escopo em cada arquivo .cpp que inclui direta ou indiretamente esse cabeçalho.
Arquivo de cabeçalho de exemplo
O seguinte exemplo mostra os vários tipos de declarações e definições permitidas em um arquivo de cabeçalho:
// sample.h
#pragma once
#include <vector> // #include directive
#include <string>
namespace N // namespace declaration
{
inline namespace P
{
//...
}
enum class colors : short { red, blue, purple, azure };
const double PI = 3.14; // const and constexpr definitions
constexpr int MeaningOfLife{ 42 };
constexpr int get_meaning()
{
static_assert(MeaningOfLife == 42, "unexpected!"); // static_assert
return MeaningOfLife;
}
using vstr = std::vector<int>; // type alias
extern double d; // extern variable
#define LOG // macro definition
#ifdef LOG // conditional compilation directive
void print_to_log();
#endif
class my_class // regular class definition,
{ // but no non-inline function definitions
friend class other_class;
public:
void do_something(); // definition in my_class.cpp
inline void put_value(int i) { vals.push_back(i); } // inline OK
private:
vstr vals;
int i;
};
struct RGB
{
short r{ 0 }; // member initialization
short g{ 0 };
short b{ 0 };
};
template <typename T> // template definition
class value_store
{
public:
value_store<T>() = default;
void write_value(T val)
{
//... function definition OK in template
}
private:
std::vector<T> vals;
};
template <typename T> // template declaration
class value_widget;
}