constexpr функции сквозь года
#новичкам
constexpr бывают не только переменные, но и функции. Такие функции могут быть выполнены, как в compile-time, так и в runtime, в зависимости от контекста вызова:
- Если значения параметров возможно посчитать на этапе компиляции, то возвращаемое значение также должно посчитаться на этапе компиляции.
- Если значение хотя бы одного параметра будет неизвестно на этапе компиляции, то функция будет запущена в runtime.
- Если вы попытаетесь присвоить возвращаемое значение функции с runtime аргументом constexpr переменной, то получите ошибку компиляции.
Пройдемся по стандартам языка и посмотрим, как в них изменялись constexpr функции.
В С++11 можно было использовать только однострочные constexpr функции с сильными ограничениями.
В С++14 уже можно писать многострочные функции, использовать в них локальные переменные и базовые конструкции языка, кроме try-catch(там динамические аллокации, с которыми трудно в compile-time), goto(открестились и правильно сделали) и еще пары менее значимых моментов:
C++17 - constexpr лямбды. За это отдельный лайк 17-му стандарту, но помимо этого ничего существенного не привнеслось:
C++20 и выше - constexpr почти везде. Уже есть и виртуальные constexpr функции, и исключения можно в compile-time отлавливать, и большинство простых контейнеров могут быть созданы и препарированы в compile-time, и все больше алгоритмов и стандартных функции получают пометки constexpr.
constexpr функции помогают иметь единый интерфейс для runtime и compile-time вычислений. Поэтому использование таких функций может приводить к неожиданному переносу некоторых вычислений в compile-time.
В сферах, где важны ультра-мега-милипизерные задержки очень важно как можно больше действий перевести в compile-time + сильно разгрузить "разогрев" программы , чтобы оставить время выполнения только для самых важных вещей(лутание бабок).
Free up time for important things. Stay cool.
#cpp11 #cpp14 #cpp17 #cpp20
#новичкам
constexpr бывают не только переменные, но и функции. Такие функции могут быть выполнены, как в compile-time, так и в runtime, в зависимости от контекста вызова:
- Если значения параметров возможно посчитать на этапе компиляции, то возвращаемое значение также должно посчитаться на этапе компиляции.
- Если значение хотя бы одного параметра будет неизвестно на этапе компиляции, то функция будет запущена в runtime.
- Если вы попытаетесь присвоить возвращаемое значение функции с runtime аргументом constexpr переменной, то получите ошибку компиляции.
constexpr int square(int x) {
return x * x;
}
int main() {
constexpr int val = square(5); // compile-time calculations
static_assert(val == 25, "Must be 25"); // compile-time check
int arg = rand() % 25;
int res = square(arg); // runtime calculations
assert(res == arg*arg); // runtime check
constexpr int fail = square(arg); // compile error here!
}
Пройдемся по стандартам языка и посмотрим, как в них изменялись constexpr функции.
В С++11 можно было использовать только однострочные constexpr функции с сильными ограничениями.
constexpr int factorial(int n) {
// ternary operator is allowed, so recursion
return (n <= 1) ? 1 : n * factorial(n - 1);
}
В С++14 уже можно писать многострочные функции, использовать в них локальные переменные и базовые конструкции языка, кроме try-catch(там динамические аллокации, с которыми трудно в compile-time), goto(открестились и правильно сделали) и еще пары менее значимых моментов:
constexpr int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
static_assert(factorial(5) == 120, "Must be 120");
C++17 - constexpr лямбды. За это отдельный лайк 17-му стандарту, но помимо этого ничего существенного не привнеслось:
constexpr auto factorial = [](int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
};
static_assert(factorial(5) == 120, "Must be 120");
C++20 и выше - constexpr почти везде. Уже есть и виртуальные constexpr функции, и исключения можно в compile-time отлавливать, и большинство простых контейнеров могут быть созданы и препарированы в compile-time, и все больше алгоритмов и стандартных функции получают пометки constexpr.
constexpr std::string create_greeting() {
std::string s = "Hello, ";
s += "C++20!";
return s;
}
static_assert(create_greeting() == "Hello, C++20!");
constexpr функции помогают иметь единый интерфейс для runtime и compile-time вычислений. Поэтому использование таких функций может приводить к неожиданному переносу некоторых вычислений в compile-time.
В сферах, где важны ультра-мега-милипизерные задержки очень важно как можно больше действий перевести в compile-time + сильно разгрузить "разогрев" программы , чтобы оставить время выполнения только для самых важных вещей(лутание бабок).
Free up time for important things. Stay cool.
#cpp11 #cpp14 #cpp17 #cpp20
🔥30👍8❤6👎2
Висячие ссылки в лямбдах
#новичкам
Все знают, что возврат ссылки на локальный объект функции приводит к неопределенному поведению. Однако не всегда так просто можно распознать такие ситуации.
В C++11 появились лямбда-выражения, а вместе с ними ещё один способ прострелить себе причинное место.
Лямбда, захватывающая что-либо по ссылке, безопасна до тех пор, пока она не возвращается куда-либо за пределы области, в которой её создали. Как только лямбда покинула скоуп - можно начинать молиться:
Гцц и шланг пишут разный результат на консоль, что напрямую говорит об ub. Можете посмотреть тут. На варнинги об этой ситуации лучше не надеяться, потому что гцц например думает, что в коде все в порядке.
Еще более интересная ситуация с объектами и методами.
Что же здесь может провиснуть? Никаких локальных объектов в методе GetNotifier нет.
На самом деле провиснет сам объект, на котором вызывается GetNotifier. Мы его аккуратненько и довольно неявненько захватили через копию указателя this. До С++ 20 мы могли захватывать this вот так по значению и такую проблему будет очень сложно дебагать. Ситуация чуть улучшилась в С++20, мы теперь обязаны указывать
Так уже чуть проще отловить проблему.
Как это лечить? Если у вас объект класса провисает, то тут поможет только профилактика и рефакторинг.
В случае с захватом this профилактикой может быть синтаксическое ограничение использование методов, возвращающий лямбду, с помощью ref-квалификаторов методов:
Теперь вы не сможете вызвать этот метод на временном объекте, потому что удалена соответствующая перегрузка.
Конечно это вряд ли поможет в многопоточке, но это уже что-то.
Refer to actual things. Stay cool.
#cppcore #cpp11 #cpp20
#новичкам
Все знают, что возврат ссылки на локальный объект функции приводит к неопределенному поведению. Однако не всегда так просто можно распознать такие ситуации.
В C++11 появились лямбда-выражения, а вместе с ними ещё один способ прострелить себе причинное место.
Лямбда, захватывающая что-либо по ссылке, безопасна до тех пор, пока она не возвращается куда-либо за пределы области, в которой её создали. Как только лямбда покинула скоуп - можно начинать молиться:
auto make_add_n(int n) {
return [&](int x) {
return x + n; // n will become dangling reference!
};
}
auto add5 = make_add_n(5);
std::cout << add5(5) << std::endl; // UB!
Гцц и шланг пишут разный результат на консоль, что напрямую говорит об ub. Можете посмотреть тут. На варнинги об этой ситуации лучше не надеяться, потому что гцц например думает, что в коде все в порядке.
Еще более интересная ситуация с объектами и методами.
struct Task {
int id;
std::function<void()> GetNotifier() {
return [=]{
std::cout << "notify " << id << std::endl;
};
}
};
int main() {
auto notify = Task { 5 }.GetNotifier();
notify();
}
Что же здесь может провиснуть? Никаких локальных объектов в методе GetNotifier нет.
На самом деле провиснет сам объект, на котором вызывается GetNotifier. Мы его аккуратненько и довольно неявненько захватили через копию указателя this. До С++ 20 мы могли захватывать this вот так по значению и такую проблему будет очень сложно дебагать. Ситуация чуть улучшилась в С++20, мы теперь обязаны указывать
this
в списке захвата:struct Task {
int id;
std::function<void()> GetNotifier() {
return [this]{
std::cout << "notify " << id << std::endl;
};
}
};
Так уже чуть проще отловить проблему.
Как это лечить? Если у вас объект класса провисает, то тут поможет только профилактика и рефакторинг.
В случае с захватом this профилактикой может быть синтаксическое ограничение использование методов, возвращающий лямбду, с помощью ref-квалификаторов методов:
struct Task {
int id;
std::function<void()> GetNotifier() && = delete; // forbit call on temporaries
std::function<void()> GetNotifier() & {
return [this]{
std::cout << "notify " << id << std::endl;
};
}
};
Теперь вы не сможете вызвать этот метод на временном объекте, потому что удалена соответствующая перегрузка.
Конечно это вряд ли поможет в многопоточке, но это уже что-то.
Refer to actual things. Stay cool.
#cppcore #cpp11 #cpp20
2🔥27👍17❤11
История capture this
#опытным
Проследим историю захвата this в лямбду сквозь стандарты С++, там есть на что посмотреть.
С++11
Появились лямбды и в них можно захватывать this как явно, так и неявно через параметры захвата по-умолчанию. Во всех случаях захватывается
Однако не было адекватного способа захватить объект по значению aka скопировать его в лямбду.
С++14
Появилась инициализация в захвате, поэтому стал реальным захват объекта по значению:
В остальном все осталось также.
С++17
Появился захват объекта по значению! Захват по значению может быть очень важно для асинхронных операций, которые откладывают выполнение лямбд:
В этом коде UB, потому что возвращаем лямбду со ссылкой на несуществующий объект. Поменять это можно захватом объекта по значению:
В этом случае в лямбде будет храниться копия объекта и все методы будут обращаться к этому скопированному объекту.
С++20
С появлением захвата this по значению стала очень путающей семантика неявного захвата. Что reference capture &, что value capture =, по факту захватывали текущий объект по ссылке. И ничто неявно не захватывало this по значению.
Изначально в 20-м стандарте эту проблему хотели решить, просто запретив неявный захват this в любом случае. Но посидели и поняли, что для ссылочного захвата по умолчанию семантика неявного захвата ссылки на объект(чем отдаленно явняется this) корректна. А вот для захвата по значению - нет.
Поэтому начиная с С++20 мы не можем неявно захватывать this в default capture by value:
Оставлю в картинке под постом инфографику по изменениям в стандартах относительно захвата this.
Know the history. Stay cool.
#cpp11 #cpp14 #cpp17 #cpp20
#опытным
Проследим историю захвата this в лямбду сквозь стандарты С++, там есть на что посмотреть.
С++11
Появились лямбды и в них можно захватывать this как явно, так и неявно через параметры захвата по-умолчанию. Во всех случаях захватывается
&(*this)
то есть указатель на текущий объект:struct Foo {
int m_x = 0;
void func() {
int x = 0;
//Explicit capture 'this'
[this]() { /*access m_x and x*/ }();
//Implcit capture 'this'
[&]() { /*access m_x and x*/ }();
//Redundant 'this'
[&, this]() { /*access m_x and x*/ }();
//Implcit capture 'this'
[=]() { /*access m_x and x*/ }();
//Error
[=, this]() { }();
}
};
Однако не было адекватного способа захватить объект по значению aka скопировать его в лямбду.
С++14
Появилась инициализация в захвате, поэтому стал реальным захват объекта по значению:
struct Foo {
int m_x = 0;
void func() {
[copy=*this]() mutable {
copy.m_x++;
}();
}
};
В остальном все осталось также.
С++17
Появился захват объекта по значению! Захват по значению может быть очень важно для асинхронных операций, которые откладывают выполнение лямбд:
struct Processor {
//Some state data..
std::future<void> process(/*args*/) {
//Pre-process...
//Do the data processing asynchronously
return
std::async(std::launch::async,
[=](/*data*/){
/*
Runs in a different thread.
'this' might be invalidated here
*/
//process data
});
}
};
auto caller() {
Processor p;
return p.process(/*args*/);
}
В этом коде UB, потому что возвращаем лямбду со ссылкой на несуществующий объект. Поменять это можно захватом объекта по значению:
struct Processor {
std::future<void> process(/*args*/) {
return
std::async(std::launch::async,
[*this](/*data*/){
/*
Runs in a different thread.
'this' might be invalidated here
*/
//process data
});
}
};
В этом случае в лямбде будет храниться копия объекта и все методы будут обращаться к этому скопированному объекту.
С++20
С появлением захвата this по значению стала очень путающей семантика неявного захвата. Что reference capture &, что value capture =, по факту захватывали текущий объект по ссылке. И ничто неявно не захватывало this по значению.
Изначально в 20-м стандарте эту проблему хотели решить, просто запретив неявный захват this в любом случае. Но посидели и поняли, что для ссылочного захвата по умолчанию семантика неявного захвата ссылки на объект(чем отдаленно явняется this) корректна. А вот для захвата по значению - нет.
Поэтому начиная с С++20 мы не можем неявно захватывать this в default capture by value:
struct Bagel {
int x = 0;
void func() {
//OK until C++20. Warning in C++20.
[=]() { std::cout << x; }();
//Error/warning until C++20. OK in C++20.
[=, this]() { std::cout << x; }();
}
};
Оставлю в картинке под постом инфографику по изменениям в стандартах относительно захвата this.
Know the history. Stay cool.
#cpp11 #cpp14 #cpp17 #cpp20
🔥14👍10❤8🤯4
Правильно захватываем по ссылке объект в лямбду
#опытным
В недавнем посте мы рассказали о проблеме, когда лямбда, захватывающая объект по ссылке, возвращается из метода. Это потенциально может привести к тому, что объект уничтожится раньше, чем произойдет вызов лямбды, что в итоге приведет к провисшей ссылке, а значит - к UB.
Можно вместо захвата по ссылке использовать захват по значению. Но скорее всего это не поможет, так как хочется использовать тот же самый объект, из метода которого возвращалась лямбда.
Тут бы шаред поинтер использовать. Но втупую его заюзать тоже ничем не поможет:
Да, мы создали из this шареный указатель, но его контрольный блок никак не учитывает оригинальный объект!
Что делать?
Использовать миксин C++11 std::enable_shared_from_this. Это базовый CRTP класс, который предоставляет метод shared_from_this(). Если вы обернете исходный объект в std::shared_ptr, то метод shared_from_this возвращает копию объекта умного указателя, в котором находился исходный объект. Эта копия будет разделять контрольный блок с оригинальным объектом, поэтому пока жива хоть одна копия, исходный объект не разрушится. Выглядит это так:
В первой строчке main происходит много вещей:
1️⃣ Создается объект класса Task и оборачивается во временный объект умного указателя.
2️⃣ Вызывается метод GetNotifier у объекта внутри временного умного указателя, из которого возвращается лямбда с захваченной копией временного объекта.
3️⃣ До перехода к следующей строчке временный объект, созданный через make_shared, разрушается.
Но ничего страшного не происходит, потому что notify хранит в себе копию временного объекта умного указателя, а значит тебе эта лямбда владелец исходного объекта Task{5}. Поэтому при вызове этой лямбды никакого Ub и провисания ссылки нет.
Вообще, наверное пора рассказывать про CRTP, миксины и прочие нечисти шаблонов в С++. Если хотите такого, то жмякните лайкосик, а с нам посты по этим темам.
Watch your lifetime. Stay cool.
#template #cpp11
#опытным
В недавнем посте мы рассказали о проблеме, когда лямбда, захватывающая объект по ссылке, возвращается из метода. Это потенциально может привести к тому, что объект уничтожится раньше, чем произойдет вызов лямбды, что в итоге приведет к провисшей ссылке, а значит - к UB.
struct Task {
int id;
std::function<void()> GetNotifier() {
return [&]{
std::cout << "notify " << id << std::endl;
};
}
};
int main() {
auto notify = Task { 5 }.GetNotifier();
notify();
}
Можно вместо захвата по ссылке использовать захват по значению. Но скорее всего это не поможет, так как хочется использовать тот же самый объект, из метода которого возвращалась лямбда.
Тут бы шаред поинтер использовать. Но втупую его заюзать тоже ничем не поможет:
struct Task {
int id;
std::function<void()> GetNotifier() {
return [curr = std::shared_ptr<Task>(this)]{
std::cout << "notify " << curr->id << std::endl;
};
}
};
int main() {
auto notify = Task { 5 }.GetNotifier();
notify();
}
Да, мы создали из this шареный указатель, но его контрольный блок никак не учитывает оригинальный объект!
curr
будет думать, что только он и его копии будут владеть объектом, а оригинальный объект просто уничтожится и curr
будет ссылаться на невалидный объект. Получили то же самое UB.Что делать?
Использовать миксин C++11 std::enable_shared_from_this. Это базовый CRTP класс, который предоставляет метод shared_from_this(). Если вы обернете исходный объект в std::shared_ptr, то метод shared_from_this возвращает копию объекта умного указателя, в котором находился исходный объект. Эта копия будет разделять контрольный блок с оригинальным объектом, поэтому пока жива хоть одна копия, исходный объект не разрушится. Выглядит это так:
struct Task : std::enable_shared_from_this<Task> {
int id;
std::function<void()> GetNotifier() {
// Захватываем shared_ptr на текущий объект
return [self = shared_from_this()] {
std::cout << "notify " << self->id << std::endl;
};
}
};
int main() {
auto notify = std::make_shared<Task>(5)->GetNotifier();
notify(); // Теперь безопасно - объект не будет уничтожен
}
В первой строчке main происходит много вещей:
1️⃣ Создается объект класса Task и оборачивается во временный объект умного указателя.
2️⃣ Вызывается метод GetNotifier у объекта внутри временного умного указателя, из которого возвращается лямбда с захваченной копией временного объекта.
3️⃣ До перехода к следующей строчке временный объект, созданный через make_shared, разрушается.
Но ничего страшного не происходит, потому что notify хранит в себе копию временного объекта умного указателя, а значит тебе эта лямбда владелец исходного объекта Task{5}. Поэтому при вызове этой лямбды никакого Ub и провисания ссылки нет.
Вообще, наверное пора рассказывать про CRTP, миксины и прочие нечисти шаблонов в С++. Если хотите такого, то жмякните лайкосик, а с нам посты по этим темам.
Watch your lifetime. Stay cool.
#template #cpp11
2👍93❤19🔥14🤯1
Динамический полиморфизм. std::function
#новичкам
В прошлом посте поговорили, что динамический полиморфизм реализуется не только через иерархии классов и виртуальные методы.
Есть другой прекрасный инструмент - std::function. Это обертка над всеми callable объектами, которая позволяет их единообразоно вызывать. Никаких иерархий, только функциональные объекты.
Теперь в очереди хранятся какие-то вызываемые объекты. Воркеру не важно, что это за объекты. Главное, что продюсеры могут разные функциональные объекты положить в один и тот же контейнер, попутно обернув их в std::function и тем самым полностью обезличив их. А легитимность такого мува достигается за счет того, что эти объекты имеют единый интерфейс - их можно вызвать без аргументов и не получить никакого возвращаемого значения.
Уже сейчас можно заметить, что для динамического полиморфизма нужно какого-то рода type erasure(стирание типов). Структура, которая хранит полиморфные объекты, не должна иметь полную информации о конкретном типе этих объектов. Объекты лишь должны иметь какой-то общий интерфейс. И тогда тип неважен: мы можем оперировать объектами через этот общий интерфейс.
std::function довольно интересно внутри устроен. После верхнеуровневого разговора про все полиморфизмы, вернемся к нему.
Но в плюсах есть еще много примеров полиморфизма времени выполнения, о которых поговорим в следующий раз.
Extract common traits. Stay cool.
#cpp11
#новичкам
В прошлом посте поговорили, что динамический полиморфизм реализуется не только через иерархии классов и виртуальные методы.
Есть другой прекрасный инструмент - std::function. Это обертка над всеми callable объектами, которая позволяет их единообразоно вызывать. Никаких иерархий, только функциональные объекты.
void Worker(std::deque<std::function<void()>>& tasks) {
while (!tasks.empty()) {
auto task = std::move(tasks.front());
tasks.pop_front();
task(); // call callable
}
}
void Producer1(const std::string& bucket, const std::vector<std::string>& paths,
const std::shared_ptr<S3Client>& client, std::deque<std::function<void()>>& tasks) {
for (const auto& path: paths)
tasks.emplace_back([&]{
client_->Upload(bucket, path);
std::cout << "Uploaded: " << bucket << ", path " << path << std::endl;
});
}
void Producer2(const std::vector<std::string>& paths, std::deque<std::function<void()>>& tasks) {
for (const auto& path: paths)
tasks.emplace_back([&]{
std::remove(path.c_str());
std::cout << "Deleted: " << path << std::endl;
});
}
Теперь в очереди хранятся какие-то вызываемые объекты. Воркеру не важно, что это за объекты. Главное, что продюсеры могут разные функциональные объекты положить в один и тот же контейнер, попутно обернув их в std::function и тем самым полностью обезличив их. А легитимность такого мува достигается за счет того, что эти объекты имеют единый интерфейс - их можно вызвать без аргументов и не получить никакого возвращаемого значения.
Уже сейчас можно заметить, что для динамического полиморфизма нужно какого-то рода type erasure(стирание типов). Структура, которая хранит полиморфные объекты, не должна иметь полную информации о конкретном типе этих объектов. Объекты лишь должны иметь какой-то общий интерфейс. И тогда тип неважен: мы можем оперировать объектами через этот общий интерфейс.
std::function довольно интересно внутри устроен. После верхнеуровневого разговора про все полиморфизмы, вернемся к нему.
Но в плюсах есть еще много примеров полиморфизма времени выполнения, о которых поговорим в следующий раз.
Extract common traits. Stay cool.
#cpp11
❤25👍14🔥6
std::getenv
#новичкам
Переменные окружения - это пары "ключ-значение", которые хранятся в операционной системе и доступны всем процессам. Они часто используются для:
- Конфигурации приложений
- Хранения чувствительных данных (паролей, ключей API)
- Управления поведением программ
Самый банальный пример - PATH. В этой переменной окружения находится список путей для поиска исполняемых файлов системой. Добавив путь к своей утилите в эту переменную, вы сможете ее запускать без указания полного пути.
Или например, более приближеный к плюсам пример, LD_LIBRARY_PATH. Это список путей, в котором линкер ищет указанные при линковке библиотеки.
И мы можем прочитать из плюсового кода переменные окружения с помощью С++11 функции std::getenv:
Это скоммунизженная из Сей функция, которая принимает имя переменной окружения и возвращает ее содержимое. Если искомой переменной не существует, возвращается nullptr.
Почему-то они решили возвращать неконстантный указатель, поэтому если уже в вашу голову пришла мысль как-то поменять данные по этому указателю, то не стоит этого делать. Получите UB.
При запуске программы ОС копирует переменные окружения, видимые родительскому процессу, внутрь программы и таким образом они окружение программы перестает зависеть от внешнего мира.
Допустим, пишите вы какой-нибудь свой клиент-сервер на Boost.Asio. Хочется конфигурировать клиента адресом и портом сервера извне, чтобы иметь возможность по-разному запускать клиента локально и, допустим, через docker-compose. Конфиг и его парсилку писать довольно муторно, а адекватную парсилку аргументов командной строки - еще сложнее. Даже если использовать готовые решения в виде json парсера и boost.program_options.
Вместо этих решений можно передавать креды подключения к серверу через переменную окружения:
Всего две строчки и никакой мороки! Очень удобная и полезная функция.
Explore your enviroment. Stay cool.
#cpp11
#новичкам
Переменные окружения - это пары "ключ-значение", которые хранятся в операционной системе и доступны всем процессам. Они часто используются для:
- Конфигурации приложений
- Хранения чувствительных данных (паролей, ключей API)
- Управления поведением программ
Самый банальный пример - PATH. В этой переменной окружения находится список путей для поиска исполняемых файлов системой. Добавив путь к своей утилите в эту переменную, вы сможете ее запускать без указания полного пути.
Или например, более приближеный к плюсам пример, LD_LIBRARY_PATH. Это список путей, в котором линкер ищет указанные при линковке библиотеки.
И мы можем прочитать из плюсового кода переменные окружения с помощью С++11 функции std::getenv:
#include <cstdlib>
char* std::getenv(const char* name);
Это скоммунизженная из Сей функция, которая принимает имя переменной окружения и возвращает ее содержимое. Если искомой переменной не существует, возвращается nullptr.
Почему-то они решили возвращать неконстантный указатель, поэтому если уже в вашу голову пришла мысль как-то поменять данные по этому указателю, то не стоит этого делать. Получите UB.
При запуске программы ОС копирует переменные окружения, видимые родительскому процессу, внутрь программы и таким образом они окружение программы перестает зависеть от внешнего мира.
Допустим, пишите вы какой-нибудь свой клиент-сервер на Boost.Asio. Хочется конфигурировать клиента адресом и портом сервера извне, чтобы иметь возможность по-разному запускать клиента локально и, допустим, через docker-compose. Конфиг и его парсилку писать довольно муторно, а адекватную парсилку аргументов командной строки - еще сложнее. Даже если использовать готовые решения в виде json парсера и boost.program_options.
Вместо этих решений можно передавать креды подключения к серверу через переменную окружения:
#include <boost/asio.hpp>
#include <iostream>
#include <cstdlib>
using boost::asio::ip::tcp;
int main() {
try {
// HERE
const char* host = std::getenv("SERVER_HOST");
const char* port = std::getenv("SERVER_PORT");
if (!host || !port) {
std::cerr << "Please set SERVER_HOST and SERVER_PORT environment variables\n";
return 1;
}
boost::asio::io_context io_context;
// Создаем и соединяем сокет
tcp::socket socket(io_context);
tcp::resolver resolver(io_context);
boost::asio::connect(socket, resolver.resolve(host, port));
// Отправляем тестовое сообщение
std::string message = "Hello from Boost.Asio client!\n";
boost::asio::write(socket, boost::asio::buffer(message));
// Читаем ответ (до символа новой строки)
boost::asio::streambuf response;
boost::asio::read_until(socket, response, '\n');
// Выводим ответ
std::istream is(&response);
std::string reply;
std::getline(is, reply);
std::cout << "Server replied: " << reply << std::endl;
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
return 1;
}
return 0;
}
Всего две строчки и никакой мороки! Очень удобная и полезная функция.
Explore your enviroment. Stay cool.
#cpp11
27👍34❤10🔥7❤🔥3