Поговорим о том, что не так в коде из предыдущего поста:
#include <cstdio>
void bar(char * s) {
printf("%s", s);
}
void foo() {
char s[] =
"Hi! I'm a kind of a loooooooooooooooooooooooong "
"string myself, you know...";
bar(s);
}
int main() {
foo();
}
🔞 Вопрос был про плюсовый код, но он как будто бы здесь даже не проходил. Пользоваться С++ и использовать только сишный инструментарий - идея, мягко говоря, не очень.
🔞 В bar() принимает указатель на неконстантные данные и никак их не изменяет. Стандартные правила хорошего тона - это помечать константностью параметры функции, в которой данные остаются нетронутыми.
🔞 В bar() нет никакой проверки границ. Почему-то функция надеется, что когда-нибудь она встретит null-terminator. Но этого спокойно может и не быть: передадим туда обычный массив символов и будет UB.
🔞 Каждый раз при вызове foo() мы кладем на стек то, что должно храниться в сегменте данных, где обычно хранятся строковые литералы. То есть вместо того, чтобы по указателю ссылаться на строку, foo копирует ее на стек и дальше использует. Это ненужные действия, которые негативно сказываются на производительности. Если конечно мы вообще можем говорить о производительности в рамках этого кода.
Как мог бы выглядеть бы код на современных плюсах?
#include <print>
#include <string_view>
void bar(std::string_view s) {
std::println("{}", s);
}
void foo() {
constexpr std::string_view s =
"Hi! I'm a kind of a loooooooooooooooooooooooong "
"string myself, you know...";
bar(s);
}
int main() {
foo();
}
Всего 2 простых улучшения:
✅ Использование легковестного std::string_view из С++17. Это по сути просто указатель + размер данных, так что накладные расходы на этот объект минимальны. А еще его даже рекомендуют передавать в функции по значению.
✅ Вместо сишной вариабельной нетипобезопасной функции printf используем типобезопасную плюсовую std::println на вариабельных шаблонах из С++23.
Простые улучшения, но в итоге все неприятности пофиксили. Магия С++.
Believe in magic. Stay cool.
#cppcore #cpp23 #cpp17