Что касается нашего примера, компоновщик найдет символ process_monitor::standard_string
в каждом модуле, который включает файл foo_lib.hpp
. Без ключевого слова inline
он не будет знать, какой символ выбрать, так что прекратит работу и сообщит об ошибке. Это же верно и для символа global_process_monitor
. Как же выбрать правильный символ?
При объявлении обоих символов с помощью ключевого слова inline
компоновщик просто примет первое вхождение символа и отбросит остальные.
До появления C++17 единственным явным способом сделать это было предоставление символа с помощью дополнительного файла модуля C++, что заставляло пользователей библиотеки включать данный файл на этапе компоновки.
Ключевое слово inline
по традиции выполняет и другую задачу. Оно указывает компилятору, что он может избавиться от вызова функции, взяв ее реализацию и поместив в то место, из которого функция вызывается. Таким образом, вызывающий код содержит на один вызов функции меньше — считается, что такой код работает быстрее. Если функция очень короткая, то полученный ассемблерный код также будет короче (предполагается, что количество инструкций, которые выполняют вызов функции, сохранение и восстановление стека и т.д., превышает количество строк с полезной нагрузкой). Если же встраиваемая функция очень длинная, то размер бинарного файла увеличится, а это не ускоряет работу программы. Поэтому компилятор будет использовать ключевое слово inline
как подсказку и может избавиться от вызовов функций, встраивая их тело. Он даже может встроить отдельные функции, которые программист не объявлял встраиваемыми.
Дополнительная информация
Одним из способов решения такой задачи до появления C++17 было создание функции static
, которая возвращает ссылку на объект static
:
class foo {
public:
static std::string& standard_string() {
static std::string s {"some standard string"};
return s;
}
};
Подобным образом вы можете совершенно легально включить заголовочный файл в несколько модулей и при этом получать доступ к одному и тому же экземпляру отовсюду. Однако объект не создается немедленно при старте программы — это происходит только при первом вызове функции-геттера. В некоторых случаях это может оказаться проблемой. Представьте, будто нужно, чтобы конструктор статического объекта, доступного глобально, при запуске программы выполнял некую важную операцию (в точности как наш класс-пример), но мы не получаем желаемого из-за вызова геттера ближе к концу программы.
Проблему можно решить еще одним способом: сделав класс foo
шаблонным и воспользовавшись преимуществами шаблонов.
В C++17 оба варианта становятся неактуальны.
Реализуем вспомогательные функции с помощью выражений свертки
Начиная с C++11, в языке появились пакеты параметров для шаблонов с переменным количеством аргументов. Такие пакеты позволяют реализовывать функции, принимающие переменное количество параметров. Иногда эти параметры объединяются в одно выражение, чтобы на его основе можно было получить результат работы функции. Решение этой задачи значительно упростилось с выходом C++17, где появились выражения свертки.
Как это делается
Реализуем функцию, которая принимает переменное количество параметров и возвращает их сумму.
1. Сначала определим ее сигнатуру:
template <typename Ts>
auto sum(Ts ts);
2. Теперь у нас есть пакет параметров ts
, функция должна распаковать все параметры и просуммировать их с помощью выражения свертки. Допустим, мы хотим воспользоваться каким-нибудь оператором (в нашем случае +
) вместе с ..., чтобы применить его ко всем значениям пакета параметров. Для этого нужно взять выражение в скобки:
template <typename Ts>
auto sum(Ts ts)
{
return (ts + ...);
}
3. Теперь можно вызвать функцию следующим образом:
int the_sum {sum(1, 2, 3, 4, 5)}; // Значение: 15
4. Она работает не только с целочисленными типами; можно вызвать ее для любого типа, реализующего оператор +
, например std::string
:
std::string a {"Hello "};
std::string b {"World"};