← Назад к вопросам

Можно ли писать код в header?

1.0 Junior🔥 181 комментариев
#Язык C++#Сборка и инструменты

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Код в Header-файлах

Да, в C++ можно (и даже нужно в некоторых случаях) писать код в header-файлы (.h). Однако, нужно соблюдать определённые правила, чтобы избежать проблем с линковкой и переопределением символов.

Что можно помещать в header?

1. Инлайн функции (inline functions)

// math.h
#ifndef MATH_H
#define MATH_H

// Инлайн функция — определяем в header
inline int add(int a, int b) {
    return a + b;
}

// С C++17, inline по умолчанию
inline int multiply(int a, int b) {
    return a * b;
}

#endif

Ключевое слово inline говорит компилятору:

  • Разместить код функции прямо на месте вызова (если возможно)
  • Позволить наличие определения функции в нескольких translation units (без ошибки линковки)

2. Функции-шаблоны (template functions)

Шаблоны обязательно должны быть полностью определены в header, потому что компилятор должен их инстанцировать в каждом месте использования.

// template_math.h
#ifndef TEMPLATE_MATH_H
#define TEMPLATE_MATH_H

// Шаблон функции
template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

template<typename T>
T min(T a, T b) {
    return (a < b) ? a : b;
}

// Явная специализация (для оптимизации конкретного типа)
template<>
const char* max(const char* a, const char* b) {
    return (std::strcmp(a, b) > 0) ? a : b;
}

#endif

3. Классы и структуры

Определения классов всегда помещаются в header. Однако, методы можно реализовать либо в header (как инлайн), либо в .cpp файле.

// point.h
#ifndef POINT_H
#define POINT_H

class Point {
private:
    double x, y;
    
public:
    Point(double x, double y);  // Объявление
    
    // Инлайн метод (определение в header)
    double get_x() const {
        return x;
    }
    
    // Просто объявление
    double distance_to(const Point& other) const;
};

#endif
// point.cpp
#include "point.h"
#include <cmath>

Point::Point(double x, double y) : x(x), y(y) {}

double Point::distance_to(const Point& other) const {
    double dx = x - other.x;
    double dy = y - other.y;
    return std::sqrt(dx * dx + dy * dy);
}

4. Шаблоны классов (class templates)

Шаблоны классов тоже полностью определяются в header.

// vector.h
#ifndef VECTOR_H
#define VECTOR_H

#include <stdexcept>

template<typename T>
class Vector {
private:
    T* data;
    size_t capacity;
    size_t size;
    
public:
    Vector() : data(nullptr), capacity(0), size(0) {}
    
    void push_back(const T& value) {
        if (size >= capacity) {
            resize_capacity();
        }
        data[size++] = value;
    }
    
    T& operator[](size_t index) {
        if (index >= size) {
            throw std::out_of_range("Index out of range");
        }
        return data[index];
    }
    
    size_t get_size() const {
        return size;
    }
    
private:
    void resize_capacity() {
        size_t new_capacity = capacity == 0 ? 1 : capacity * 2;
        T* new_data = new T[new_capacity];
        for (size_t i = 0; i < size; i++) {
            new_data[i] = data[i];
        }
        delete[] data;
        data = new_data;
        capacity = new_capacity;
    }
};

#endif

5. constexpr функции и переменные

Функции constexpr вычисляются на этапе компиляции и могут быть в header.

// math_const.h
#ifndef MATH_CONST_H
#define MATH_CONST_H

// Это будет вычислено на этапе компиляции
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

constexpr double PI = 3.14159265;

constexpr int fibonacci(int n) {
    return n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}

#endif
// main.cpp
#include "math_const.h"
#include <iostream>

int main() {
    // Все вычисляется в compile-time!
    constexpr int fact = factorial(5);  // 120
    constexpr int fib = fibonacci(10);  // 55
    
    std::cout << "5! = " << fact << std::endl;
    std::cout << "fib(10) = " << fib << std::endl;
    
    return 0;
}

6. static переменные и функции

static функции и переменные могут быть в header (будут разные копии в каждом translation unit).

// logger.h
#ifndef LOGGER_H
#define LOGGER_H

#include <iostream>

static void log(const std::string& message) {
    std::cout << "[LOG] " << message << std::endl;
}

static int counter = 0;  // Будет своя копия в каждом .cpp

#endif

Проблемы: что НЕ нужно в header

1. Обычные (неинлайн) функции

// BAD! Не делай так:
// utils.h
int add(int a, int b) {
    return a + b;
}

Если этот header включить в несколько .cpp файлов, функция add будет определена несколько раз → linker error!

2. Глобальные переменные

// BAD!
// globals.h
int global_counter = 0;  // Несколько определений!

Правильный способ:

// globals.h
extern int global_counter;  // Только объявление!
// globals.cpp
int global_counter = 0;  // Определение в одном месте

3. Использование using namespace в header

// BAD!
// utils.h
using namespace std;  // Загрязняет namespace включаемого файла!

int add(int a, int b);  // Теперь int в глобальном namespace

Лучшие практики

1. Используй include guards или #pragma once

// math.h
#ifndef MATH_H
#define MATH_H

// ...

#endif

Или современный способ:

// math.h
#pragma once

// ...

2. Помещай в header только необходимое

// shape.h
#pragma once

#include <iostream>

class Shape {
public:
    virtual ~Shape() = default;
    
    // Объявление
    virtual void draw() const;
    virtual double area() const = 0;
};
// shape.cpp
#include "shape.h"

void Shape::draw() const {
    std::cout << "Drawing shape" << std::endl;
}

3. Инлайнируй часто вызываемые функции

// point.h
pragma once

class Point {
private:
    int x, y;
    
public:
    // Инлайн — часто вызывается
    int get_x() const { return x; }
    int get_y() const { return y; }
    
    // Не инлайн — редко вызывается
    void expensive_calculation();
};

4. С C++20: Используй modules вместо header

// math.cppm
export module math;

export constexpr int add(int a, int b) {
    return a + b;
}

export template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}
// main.cpp
import math;
#include <iostream>

int main() {
    std::cout << add(5, 3) << std::endl;  // 8
    std::cout << max(10.5, 8.3) << std::endl;  // 10.5
    return 0;
}

Итог

В header можно и нужно писать:

  • Инлайн функции (inline)
  • Шаблоны (template functions и classes)
  • constexpr функции
  • Определения классов (методы как инлайн или отдельно в .cpp)

НЕ пиши в header:

  • Обычные (неинлайн) функции
  • Глобальные переменные (только extern объявления)
  • using namespace

Приватная реализация деталей логики идёт в .cpp, определения и интерфейсы — в .h.