Грокаем C++
رفتن به کانال در Telegram
Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов. По всем вопросам (+ реклама) @ninjatelegramm Менеджер: @Spiral_Yuri Реклама: https://telega.in/c/grokaemcpp Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
نمایش بیشتر9 386
مشترکین
+924 ساعت
+147 روز
+1430 روز
آرشیو پست ها
9 385
Третий пошел
struct SomeClass {
// void foo() & {std::cout << "Call on lvalue reference" << std::endl;} //1
void foo() const & {std::cout << "Call on const lvalue reference" << std::endl;} //2
// void foo() && {std::cout << "Call on rvalue reference" << std::endl;} //3
void foo() const && = delete; //4
};
int main() {
SomeClass lvalue;
lvalue.foo();
SomeClass{}.foo();
}9 385
Ответ:
Вызовутся методы 2 и 4 по порядку. rvalue reference может приводиться к константной левой ссылке, но также может приводиться к const rvalue ref. Второе преобразование достигается меньшими усилиями, поэтому вызовется 4 метод.
9 385
Второй пошел
struct SomeClass {
// void foo() & {std::cout << "Call on lvalue reference" << std::endl;} //1
void foo() const & {std::cout << "Call on const lvalue reference" << std::endl;} //2
// void foo() && {std::cout << "Call on rvalue reference" << std::endl;} //3
void foo() const && {std::cout << "Call on const rvalue reference" << std::endl;} //4
};
int main() {
SomeClass lvalue;
lvalue.foo();
std::move(lvalue).foo();
}9 385
Ответ: Вызовутся методы 2 и 3 по порядку. За неимением неконстантной перегрузки для левых ссылок, остается только константная перегрузка для первого вызова.Во втором случае rvalue reference может приводиться к константной левой ссылке, но в этот раз есть более подходящие кандидаты на перегрузку. И самым подходящим будет 3 метод.
9 385
Мини-квизы
Сейчас пойдет пачка мини-квизов на проверку того, как хорошо вы понимаете выбор ref-qualified перегрузок. Для меньшей запутанности я буду оставлять все перегрузки в тексте кода, но закомменчу ненужные в каждом случае. Также подключены все необходимые инклюды и компиляция происходит под 17-й стандарт.
В режиме опроса сложно указывать варианты с переносом строк. Поэтому на месте, где должен быть перенос буду ставить"\n".
Ответы выложу вечером.
Первый пошел:
struct SomeClass {
// void foo() & {std::cout << "Call on lvalue reference" << std::endl;} //1
void foo() const & {std::cout << "Call on const lvalue reference" << std::endl;} //2
void foo() && {std::cout << "Call on rvalue reference" << std::endl;} //3
void foo() const && {std::cout << "Call on const rvalue reference" << std::endl;} //4
};
int main() {
SomeClass lvalue;
lvalue.foo();
SomeClass{}.foo();
}9 385
sysconf 2025 — конференция по системному программированию от создателей C++ Russia и DevOops
📅 22 марта в Москве + онлайн
Вас ждут 22 спикера, 19 докладов и Lightning Talks. Разберемся на реальных кейсах, как устроены многопоточные рантаймы, компиляторы и низкоуровневые оптимизации. Подробнее — в расписании.
🎟 Билеты уже на сайте. Если оплачиваете самостоятельно — промокод
GROKAEMCPP дает скидку 15%.
Реклама. ООО «Джуг Ру Груп». ИНН 78013414469 385
Всем привет!
Давайте проведем небольшой опрос, насколько опытные плюсовики у нас тут присутствуют. Так мы сможем более адекватно подстраивать контент под аудиторию.
Как вы оцениваете свой уровень владения С++?
9 385
Совмещаем ссылочные и cv квалификаторы методов
Далеко не всегда очевидно, какая именно функция является лучшим кандидатом для перегрузки в том или ином случае. Да, когда это только & или &&, то все довольно просто. Но что получается, когда мы добавим константность методам?
Компилятор будет выбирать подходящую перегрузку по определенному алгоритму.
struct SomeClass {
void foo() & {std::cout << "Call on lvalue reference" << std::endl;} //1
void foo() const & {std::cout << "Call on const lvalue reference" << std::endl;} //2
void foo() && {std::cout << "Call on rvalue reference" << std::endl;} //3
void foo() const && {std::cout << "Call on const rvalue reference" << std::endl;} //4
};
Дело в том, что все правые ссылки могут каститься к const lvalue reference, а левые к правым - ни при каких обстоятельствах. Неконстантные типы могут каститься к константным. И никак наоборот.
Исходя из этих правил компилятор и разрешает перегрузки. Пометил методы из примера порядковыми номерами, чтобы потом было легче делать отсылки
SomeClass lvalue;
const SomeClass const_lvalue;
lvalue.foo();
const_lvalue.foo();
В случае lvalue.foo() вызываем перегрузку для неконстантной левой ссылки. Левые ссылки не могут приводиться к правым. Поэтому методы под номерами 3 и 4 не подходят.
Неконстантные типы могут приводиться к константным. Поэтому нам подходят и 1, и 2 методы. Однако для вызова 2 придется сделать шажок - добавить константности, а для вызова первого - ничего. Поэтому выбирается первая перегрузка.
В случае const_lvalue.foo() вызываем перегрузку для константной левой ссылки. 3 и 4 также откидываем по тем же причинам. Однако в этот раз нам подходит лишь 2 перегрузка, так как константный тип не может быть приведен к неконстантному.
Итого вывод получится такой:
Call on lvalue reference Call on const lvalue referenceДля rvalue ссылки нехитрыми рассуждениями можно прийти к правильному ответу о вызываемой перегрузке
SomeClass.foo(); // OUTPUT // Call on rvalue referenceТут довольно все просто. Но самая жесть начинается, когда у нас нет какой-то перегрузки/перегрузок из полного набора. На следующей неделе забомбардирую вас мини-квизами на эту тему. Посмотрим, как хорошо вы шарите за overload resolution. Choose the right way. Stay cool. #cppcore
9 385
Открытый вебинар «Архитектурные решения в Backend-разработке»
📚На вебинаре вы узнаете:
1. Как выбрать архитектурный стиль в зависимости от требований к производительности, масштабируемости и отказоустойчивости.
2. Микросервисы vs монолит: плюсы и минусы, примеры смены подходов.
3. Событийно-ориентированная архитектура: когда и как ее использовать, основные принципы и инструменты.
4. CQRS и источник событий: как управлять данными в сложных условиях.
5. Как проектировать отказоустойчивые серверные системы, включая прерывание автоматического выключателя, повторную попытку, переборку и другие схемы.
6. Ключевые ошибки при проектировании конструкции и как их избежать.
👨💻Кому будет полезно?
+ Разработчикам Backend
+ Разработчикам FullStack
+ Системным аналитикам
⏰ 18 марта в 20:00 (мск).
🆓Бесплатно. Вебинар в рамках курса «Software Architect»
👉Записывайтесь:https://otus.pw/qoYh4/?erid=2W5zFHYn66t
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
#реклама
О рекламодателе
9 385
ref-qualified методы
#опытным
В С++ можно довольно интересными способами перегружать методы класса. Один из самых малоизвестных и малоиспользуемых - помечать методы квалификатором ссылочности.
Чтобы было понятнее. Примерно все знают, что бывают константные и неконстантные методы.
struct SomeClass {
void foo() {std::cout << "Non-const member function" << std::endl;}
void foo() const {std::cout << "Const member function" << std::endl;}
};
SomeClass nonconst_obj;
const SomeClass const_obj;
nonconst_obj.foo();
const_obj.foo();
// OUTPUT
// Non-const member function
// Const member function
Константные объекты могут вызывать только константные методы. Поэтому мы можем перегрузить метод класса, чтобы он мог работать с константными объектами.
В примере видно что у константного объекта вызывается константная перегрузка.
По аналогии с cv-квалификаторами методов начиная с С++11 существуют ref-квалификаторы. Мы можем перегрузить метод так, чтобы он мог раздельно обрабатывать левые и правые ссылки.
struct SomeClass {
void foo() & {std::cout << "Call on lvalue reference" << std::endl;}
void foo() && {std::cout << "Call on rvalue reference" << std::endl;}
};
SomeClass lvalue;
lvalue.foo();
SomeClass{}.foo();
// OUTPUT
// Call on lvalue reference
// Call on rvalue reference
Обратим внимание на сигнатуру методов. Метки ссылочных квалификаторов ожидаемо принимают форму одного и двух амперсандов, по аналогии с типами данных левых и правых сслылок соотвественно. Располагаются они после скобок с аргументами метода.
Работают они примерно также, как вы и ожидаете. lvalue-ref перегрузка вызывается на именованном объекте, rvalue-ref перегрузка - на временном.
Зачем это придумано?
Здесь на самом деле большие параллели с cv-квалификацией методов. Допустим, у вас класс - это какая-то коллекция. И вы хотите давать пользователям доступ к элементам этой коллекции через оператор[]. Для неконстантных объектов удобно возвращать ссылку. А вот для константных возвращение ссылки - потенциальное нарушение неизменяемости объекта. Поэтому в таких случаях константный оператор может возвращать элемент по значению.
Также и с ссылочностью. В каких-то случаях оптимально или просто необходимо использовать для правых ссылок иную логику метода.
Подробнее об этом чуде-юде будем разбираться в следующих постах.
Stay flexible. Stay cool.
#cpp11 #design9 385
std::array
#новичкам
На самом деле, это очень-очень тонкая обертка над сишными массивами. Вот несколько упрощенная реализация, которая тем не менее полностью передает смысл и необходимые особенности.
template<typename T, size_t N>
struct array
{
T& operator[](size_t index) {
return _data[index];
}
T& front() {
return _data[0];
}
T& back() {
return _data[N];
}
T* data() {
return _data;
}
constexpr size_t size() const {
return N;
}
constexpr bool empty() const {
return N == 0;
}
// еще const версии перечисленных методов и некоторые другие методы и алиасы типов
private:
T _data[N];
};
За счет использования шаблоного типа нижележащего массива std::array может работать с любыми встроенными и кастомными типами.
А за счет нетипового шаблонного аргумента N, std::array знает количество элементов, которое в нем находится, еще на этапе компиляции!. И не нужно ничего вычислять! Достаточно вызвать метод size(), который буквально constexpr.
std::array arr{1, 2, 3};
static_assert(arr.size() == 3); // здесь не упадем
Обычно удобство абстракций идет вместе с платой за это удобство. Но это не тот случай. За счет того, что все методы std::array буквально занимают одну строчку, компилятору очень удобно инлайнить их код в caller'ов. Это приводит к тому, что низкоуровневый ассемблерный код при работе с C-style массивами и std::array практически всегда идентичен.
std::array не мимикрирует ни под какой другой тип, так как это кастомный класс. Внутри себя он также инкапсулирует все необходимые операторы сравнения. В операциях с ним нет никакой путаницы, потому что они явно определены конкретно для этого класса. Его можно спокойно принимать в функцию по ссылке и по значению, а также указывать в качестве возвращаемого значения. И все это с привычной семантикой.
template<typename T, size_t N>
std::array<T, N> double_elements(std::array<T, N>& array) {
std::array<T, N> result = array;
for (auto& elem: result)
elem = elem * 2;
return result;
}
Если мы создаем массив в локальной области функции(99% случаев), то элементы std::array располагаются непрерывно на стеке. И размер std::array равен размеру C-style массива с одинаковым количеством элементов и их типом.
int c_arr[N];
std::arrray<int, N> cpp_arr;
sizeof(cpp_arr) == cpp_arr.size() * sizeof(int) ==
sizeof(c_arr) == N * sizeof(int) == std::size(c_arr) * sizeof(int);
Итак. Выходит, что std::array идентичен сишному массиву по внутреннему устройству и произодительности, да еще и решает все проблемы неумелого использования последнего. Идеальный высокоуровневый инструмент!
Так что std::array должен быть первым выбором в случае необходимости создания массива с длиной, известной на этапе компиляции. У PVS-Studio есть прекрасная статья на этот счет.
Fix your flaws. Stay cool.
#STL #cppcore9 385
Всем привет!👋 Мы занимаемся разработкой приложения, которое соединяет людей.
Мы хотим понять, что действительно нужно людям, какие функции могут сделать приложение полезным, удобным и приятным.
Опрос займет всего 5–7 минут. 🕒
Все ответы анонимны, а ваши идеи и пожелания помогут нам создать приложение, которое станет вашим лучшим помощником в поиске общения и интересных знакомств.
Заранее благодарим за помощь!
🎁В конце вас будет ждать приятный подарок😊
Пройти опрос ⬅️
9 385
Проблемы С-style массивов
#опытным
В наследство от языка С С++ достались статические массивы. Так называемые С-style массивы. Это проверенные средства языка, успешно решающие свои задачи. Но у них есть серьезные недостатки, которые в основном связаны с низкоуровневостью этого инструмента.
Давайте кратко повторим, что такое C-style массив.
Это непрерывная последовательность элементов одного типа и память под них выделяется на стеке и автоматически освобождается при выходе из функции.
Определяется сишный массив вот так:
// создаем массив на 5 элементов,
// которые по умолчанию инициализируются нулями
int arr1[5];
// создаем массив и предоставляем набор элементов, с помощью
// которых компилятор вычисляет длину массива и инициализирует элементы
int arr2[] = {1, 2, 3, 4, 5};
Размер памяти, занимаемый массивом равняется количеству его элементов помноженному на размер типа данных:
constexpr size_t array_size = 5;
int arr[array_size];
sizeof(arr) == array_size * sizeof(int); // true
Соответственно, для получения количества элементов массива, нужно поделить sizeof от массива на размер типа данных, которые он хранит.
auto array_size = sizeof(arr) / sizeof(Type);
В чем же его недостатки?
❗️ Массивы нельзя сравнивать напрямую, а только поэлементно. Напрямую сравниваются указатели на первый элемент.
int arr1[] = {0, 1, 2, 3};
int arr2[] = {0, 1, 2, 3};
// ложь так как сраниваются указатели,
// а они разные для разных объектов
arr1 == arr2;
❗️ Мимикрирование под массивы разрешает странную семантику с условиями и арифметическими операциями.
// создаем пустую строку в виде массива
char arr[] = "";
// условие будет всегда true, хотя мы создали пустую строку
if (arr);
// разрешается, но зачем? что значит прибавить к массиву число?
arr + 1;
❗️В С разрешены [массивы переменной длины](https://t.me/grokaemcpp/56) на уровне стандарта. И синтаксис у них ровно такой же, как и у статических массивов, только при его создании размер указывается не константой, а переменной. В С++ это не стандартная фича, а расширения компилятора. То есть нельзя писать кроссплатформенный код с использованием массивов переменной длины. Но за счет идентичного синтаксиса очень легко спутать один вид массива с другим и похерить переменосимость.
❗️ От синтаксиса сочетания функций и массивов хочется вырвать себе глаза, закрыть компьютер и уйти жить в лес:
int foo(int arr[4]);
// На самом деле такая сигнатура полностью эквивалентна int foo(int * arr),
// что позволяет принимать в функцию массив любой длины и указатели.
// В С++ нет синтаксиса приема массива по значению.
void foo(int (&arr)[4]); // зато есть синтаксис приема массива по ссылке
// Нормального синтаксиса для возврата массива из функции также не завезли.
// Вот воркэраунды.
int get_array()[10];
auto get_array() -> int[10];
❗️Массив не инкапсулирует в себе свой размер. Его нужно всегда вычислять, как мы говорили в начале.
❗️Из-за сложности синтаксиса, вы скорее всего захотите обрабатывать массивы с помощью функций с похожей сигнатурой:
void foo(int * p, size_t size);
Это потенциально может привести к доступу за границы выделенной области, так как функция foo ничего не знает про то, какой реальный размер имеет область памяти, на которую указывает p. Она должна доверять программисту и переданному значению size. А программисту верить - себя не уважать.
В общем, сишные массивы - это не объекты и не обладают преимуществами ООП и универсальной семантики для объектов в С++.
Поэтому стандартная библиотека предоставляет нам инструмент, который решает все проблемы C-style массивов. Это контейнер std::array. О нем мы поговорим в следующий раз.
Upgrade your tools. Stay cool.
#cppcore #goodoldc9 385
Когда мы вынуждены явно использовать new
#опытным
Сырые указатели - фуфуфу, бееее. Это не вкусно, мы такое не едим. new expression возвращает сырой указатель на объект. Соотвественно, мы должны максимально избегать явного использования new. У нас все-таки умные указатели и функции std::make_* довольно давно завезли.
Однако все-таки есть кейсы, когда мы просто вынуждены использовать new явно:
👉🏿 std::make_unique не может в кастомные делитеры. Если хотите создать уникальный указатель со своим удалителем - придется использовать new.
auto ptr = std::unique_ptr<int, void()(int)>(new int(42), [](int* p) {
delete p;
std::cout << "Custom deleter called!\n";
});
👉🏿 Приватный конструктор у класса. Странно вообще пытаться создать объект такого класса, но не торопитесь. Приватный конструктор может быть нужен, чтобы оставить только один легальный способ создания объекта - фабричную функцию Create. Она возвращает уникальный указатель на объект и обычно является статическим членом класса. Функция Create имеет доступ к приватным методам, поэтому может вызвать конструктор. Но вот std::make_unique ничего не знает о приватных методах класса и не сможет создать объект. Придется использовать new.
struct Class {
static std::unique_ptr<Class> Create() {
// return std::make_shared<Class>(); // It will fail.
return std::unique_ptr<Class>(new Class);
}
private:
Class() {}
}
👉🏿 Жизнь без 20-го стандарта. До 20-го стандарта вы не могли создать объект POD класса без указания фигурных скобок. Но именно так и делает std::make_unique.
То есть вот так нельзя делать в С++17:
struct MyStruct {
int a, b, c;
};
auto ptr = std::make_unique<MyStruct>(1, 2, 3); // Will fail C++17
auto ptr = std::unique_ptr<MyStruct>(new MyStruct{1, 2, 3}); // Norm
Но можно в С++20. Так что тем, кто необновился, придется использовать new.
В целом, все. Если что забыл - накидайте в комменты.
Но помимо этого, администрация этого канала не рекомендует в домашних и рабочих условиях явно вызывать new. Это может привести к потери конечности(отстрелу ноги).
Stay safe. Stay cool.
#cppcore #memory #cpp20 #cpp179 385
Ты умеешь кодить, создавать телеграм-ботов,
знаешь много теории, есть потенциал,
но не знаешь где применить свои навыки и начать работать?
Тогда тебе стоит подать заявку в FUNDAMENT,
Это новый проект готовый приступать работать. Мы ищем программистов и сотрудников что бы запускаться!
Заходи в @ruFUNDAMENT и отправляй заявку (рассматриваем все)
9 385
emplace_back vs push_back
#новичкам
Раз уж такая масленица пошла, расскажу про весь сыр-бор с методами вектора(да и не только вектора).
В последовательные контейнеры можно запихнуть данные в конец двумя способами: метод push_back и метод emplace_back.
template< class... Args >
reference emplace_back( Args&&... args ); // returns ref to created element
void push_back( const T& value );
void push_back( T&& value );
По сигнатуре видно, что они предназначены немного для разного.
Начнем со сложного. emplace_back принимает пакет параметров. Эти параметры предполагаются как аргументы конструктора хранимого типа T. Реализован он примерно так:
template <typename... Args>
reference emplace_back(Args&&... args) {
if (size == capacity) grow();
return *new (start + size++) T(std::forward<Args>(args)...);
}
Если надо, то расширяемся и делаем placement new на участке памяти для нового объекта, попутно используя perfect forwarding для передачи аргументов в конструктор. Вот тут кстати те самые круглые скобки используются, которые не давали pod типам нормально конструироваться.
push_back принимает ссылку на уже готовый объект. То есть объект должен быть создан до входа в метод. И на основе этого значения уже конструируется объект в контейнере. В простейшем случае push_back вызывает внутри себя emplace_back:
void push_back(T&& value) {
emplace_back(std::move(value));
}
Чтобы вызвать пуш бэк нужно вызвать 2 конструктора: от аргументов и copy|move. Для emplace_back же нужен только один конструктор - от аргументов.
То есть emplace_back банально эффективнее, чем push_back. Для случаев, когда мы почему-то не можем создать объект внутри emplace_back(POD типы и < С++20) мы его создаем снаружи и копируем/муваем внутрь. Тогда эффективности двух методов одинаковая.
Получается, что emplace_back в любом случае не менее эффективнее, чем push_back. Именно поэтому нужно всегда предпочитать использовать emplace_back.
Be just better. Stay cool.
#STL #memory
اکنون در دسترس! پژوهش تلگرام ۲۰۲۵ — مهمترین بینشهای سال 
