Грокаем C++
الذهاب إلى القناة على Telegram
Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов. По всем вопросам (+ реклама) @ninjatelegramm Менеджер: @Spiral_Yuri Реклама: https://telega.in/c/grokaemcpp Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
إظهار المزيد9 384
المشتركون
+924 ساعات
+147 أيام
+1430 أيام
أرشيف المشاركات
9 388
Ответ на квиз
В комментариях все более менее пришли к консенсусу, однако давайте разложим все по полочкам, чтобы вопросов уже точно не осталось.
#include <iostream>
int main () {
std::cout << +-!!"" << std::endl;
return 0;
}
Начнем с того, что этот код компилируется. Это уже много дает. По крайней мере вы смотрите на валидный С++ код.
Плюс-минусы и два восклицательных знака - валидные операции над валидными тривиальными типами. Поэтому, по факту, единственное, что надо доказать - что !"" - валидное выражение.
Разберемся с кавычками. Это строковый литерал, обозначающий пустую строку. И он имеет тип const char[1]. Единичка берется из-за того, что сишные строки неявно содержат символ '\0' в конце. Так собственно и определяется конец строки.
Для массивов не определен оператор логического отрицания. Печаль...
Но зато он определен для указателей! А у массивов есть одно замечательное свойство - косить под указатель на свой первый элемент. Поэтому для выполнения !"", компилятор приведет "" к const char *, который будет указывать на какой-то конкретный участок памяти, где лежит эта пустая строка. Раз участок конкретный - тогда этот указатель считается типа true. Отрицаем true - получаем false.
Дальше остается всего-то +-!false. Отрицает false - получаем true. Также у нас есть встроеные унарные операторы для арифметических типов. А bool - тоже арифметический тип. Поэтому true - это как бы 1. +-1 в итоге дает -1(+знака числа не меняет).
Таким нехитрым образом, получаем ответ: -1.
Ставьте снеговика☃️, если ваша интуиция(или знания) вас не подвели.
Stay on positive side. Stay cool.9 388
👩💻 C++ теперь в Telegram!
Вот обширная база контента по C++, которая ежедневно пополняется новыми постами:
Обучение C++ с нуля
Обучение Qt с нуля
Обучение Asio с нуля
Обучение Boost с нуля
Обучение FLTK с нуля
Обучение wxWidgets с нуля
📌 Ресурсы, гайды, шпаргалки, задачи и книги — всё собрано в одном месте: @cpp_ready
9 388
Квиз
Вчера в комментах наш подписчик Вячеслав скинул интересный примерчик, который здорово показывает вашу плюсовую интуицию. Поэтому захотелось разобрать его в рамках поста. Ну и для интереса предлагаю вам поучаствовать в #quiz и испытать свою интуицию. Код до боли краток, и до еще большей боли бессмысленен и ужасен. Но все же.
Какой будет результат попытки компиляции и запуска следующего кода?
#include <iostream>
int main () {
std::cout << +-!!"" << std::endl;
return 0;
}
Have a meaning in your life. Stay cool.
#fun9 388
🔥 Самые нужные каналы для C/C++ разработчика, чтобы расти в доходе 💸
• C/C++ | Вопросы собесов
• C/C++ | LeetCode
• C/C++ | Тесты
• C/C++ | Удалёнка
Подпишись, чтобы не потерять ☝️
9 388
Представление отрицательных чисел в С++
Отрицательные числа - заноза в заднице компьютеров. В нашем простом мире мы различаем положительные и отрицательные числа с помощью знака. Положительные числа состоят просто из набора цифр(ну и по желанию можно добавить + слева, никто не обидится), а к отрицательным слева приписывается минус. Но компьютеры у нас имеют бинарную логику, там все представляется в виде единиц и ноликов. И там нет никаких плюсов-минусов. Тогда если у нас модуль числа уже представляется в виде единичек и ноликов, то непонятно, как туда впихнуть минус.
Вот и в комитете по стандартизации не знали, как лучше это сделать и удовлетворить всем, поэтому до С++20 они скидывали с себя этот головняк. До этого момента С++ стандарт разрешал любое представление знаковых целых чисел. Главное, чтобы соблюдались минимальные гарантии. А именно: минимальный гарантированный диапазон N-битных знаковых целых чисел был [-2^(N-1) - 1; 2^(N-1)-1]. Например, для восьмибитных чисел рендж был бы от -127 до 127. Это соответствовало трем самым распространенным способам представления отрицательных чисел: обратному коду, дополнительному коду и метод "знак-величина".
Однако все адекватные компиляторы современности юзают дополнительный код. Поэтому, начиная с С++20, он стал единственным стандартным способом представления знаковых целых чисел с минимальным гарантированным диапазоном N-битных знаковых целых чисел [-2^(N-1); 2^(N-1)-1]. Так для наших любимых восьмибитных чисел рендж стал от -128 до 127.
Кстати для восьмибитных чисел обратной код и метод "знак-амплитуда" были запрещены уже начиная с С++11. Все из-за того, что в этом стандрате сделали так, чтобы все строковые литералы UTF-8 могли быть представлены с помощью типа char. Но есть один краевой случай, когда один из юнитов кода UTF-8 равен 0x80. Это число не может быть представлен знаковым чаром, для которого используются обратной код и метод "знак-величина". Поэтому комитет просто сказал "запретить".
Для кого-то много непонятных слов и терминов, поэтому в дальнейшем будем раскрывать все секреты представления отрицательных чисел в памяти.
Stay defined. Stay cool.
#cppcore #cpp20 #cpp11
9 388
Теория заговора
Вот живет программист по С++ своей прекрасной и беззаботной жизнью. Все у него хорошо: код пишется, баги фиксятся, деньги мутятся. И имя у него такое прекрасное - Иннокентий.
Иногда он лазается по cppreference, чтобы освежить знания по каким-то фичам или узнать что-то новое. Представим себе, что он зашел просмотреть на доку std::atoi и видит там такой фрагмент:
const auto data =
{
"42",
"0x2A", // treated as "0" and junk "x2A", not as hexadecimal
"3.14159",
"31337 with words",
"words and 2",
"-012345",
"10000000000" // note: out of int32_t range
};
Ничего необычного, просто определяется std::initializer_list<const char *> и записываются туда разные строки. Ну ладно, работает дальше.
А дальше ищет статейку по std::variant. И находит там вот какой отрывок:
int main()
{
std::variant<int, float> v, w;
v = 42; // v contains int
int i = std::get<int>(v);
assert(42 == i); // succeeds
w = std::get<int>(v);
w = std::get<0>(v); // same effect as the previous line
w = v; // same effect as the previous line
...
}
Почему-то он обратил внимание на число 42. "Где-то я его уже видел.". И вспоминает, что недавно видел это же число в коде для std::atoi. Это, конечно, немного странно - подумал, он. Но решил, что это просто случайность.
Приходит наш герой домой с работы и, как каждый уважающий себя программист, вместо ужина садится писать свой пет-проект. А вы не знали, что программисты могут годами жить на диете "код+кофе"?
Пишет он какое-то многопоточное приложение. Чтобы адекватно писать такие штуки, нужны глубокие знания о модели памяти в С++ и как работает синхронизация данных в многопроцессорном мире. Поэтому кодер снова идет на cppreference и находит там статейку про std::memory_order. Читает, читает. И херак, вылупил глаза в экран. "Это уже очень странно". А увидел он следующий фрагмент:
std::vector<int> data;
std::atomic<int> flag = {0};
void thread_1()
{
data.push_back(42);
flag.store(1, std::memory_order_release);
}
Опять это 42! "Что за приколы такие? Это что, любимое число плюсовиков, что они его везде пихают?". На том и порешил. Не нервничать же по поводу чьего-то любимого числа. Может именно на этот день рождения Страуструпу подарили маленького щеночка....
В общем, затерпели и забыли.
Иннокентий только начинает писать свой проект и ему нужен хороший сборочный скрипт. Выбор языка пал на модномолодежный питухон, поэтому он полез в документацию изучать базовый синтаксис питона. И тут на тебе:
x = int(input("Please enter an integer: "))
Please enter an integer: 42
if x < 0:
x = 0
print('Negative changed to zero')
elif x == 0:
print('Zero')
elif x == 1:
print('Single')
else:
print('More')
Whatafuck? Страуструп здесь уже никак не может быть замешан. Ситуация больше похожа на массонский заговор. Кенни не выдержал и пошел распутывать тайну века.
Оказалось, что это отсылка на книгу Дугласа Адамса "Автостопом по галактике". Там люди создали супермощный супекомпьютер только с одной целью - узнать ответ на "Главный вопрос жизни, Вселенной и всего такого". Этот вопрос настолько сложный и комплексный, что на нахождение ответа суперкомпьютер потратил целых 7.5 млн лет вычислений. И в окончании выдал: "42".
Роман вышел в период расцвета sci-fi, поэтому оставил глубокий отпечаток в массовой культуре. Оно появлялось в популярных сериалах типа "Остаться в живых". Даже один из радиотелескопов НАСА использует ровно 42 тарелки в честь отсылки к произведению.
Неудивительно, что гики по всему миру начали пихать это число во всех места в качестве пасхалки. Сейчас почти где-угодно встречая 42, вы можете быть на 99% уверены, что это именно отсылка на "Автостопом по галактике".
Так и была разгадана величайшая из тайн иллюминатов и Иннокентий довольный пошел спать. The end.
Make references to the great things. Stay cool.
#fun9 388
Магическое заклинание +[]{}
Правильный ответ на квиз - программа успешно завершится. Удивились? Погнали разбирать
Давайте сразу проясним, что лямбду можно записывать без списка параметров, это разрешенная тема. Да и работает это в целом довольно интуитивно: если ваша лямбда не принимает параметров, то вы можете их не указывать и все будет работать также, как и с пустыми круглыми скобками.
Дальше
Здесь мы рассматривали, что присвоение лямбд запрещено и приводит к ошибке компиляции. Каким образом один
+ переворачивает все сверх на голову и код работает?
Стандарт нам говорит:
The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type's function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type's function call operator.То есть ламбды без захваченых значений имеют возможность прикидываться указателями на функции. И так вышло, что оператор+ определен для всех типов указателей в виде:
For every type T there exist candidate operator functions of the form T* operator+(T*);Он ничего не делает с указателем и просто его возвращает наружу. Когда мы ставим + спереди лямбды, компилятор пытается найти подходящую перегрузку для оператора+. Логично, что для любого замыкания он не перегружен, поэтому что он не нужен там. Но зато есть перегрузка для указателя, под который может косить лямбда. Что она успешно и делает. Поэтому для здесь для test
auto test = +[]{};
auto test2 = []{};
компилятор выведет тип указателя на функцию, а не замыкания. И ничего нам не мешает перенаправлять этот указатель на другую функцию.
Итого, один бессмысленный плюс тащит катку.
Stay amazed. Stay cool.
#cpp11 #cppcore9 388
👩💻 Программирование теперь в Telegram!
Вот 10 обучающих каналов по самым востребованным направлениям в IT.
Выбирай своё направление:
👩💻 C/C++: @cpp_ready
👩💻 C#: @csharp_ready
👩💻 Python: @python_ready
👩💻 Java: @java_ready
📖 Общее IT: @roadmap_ready
🖥 Базы Данных & SQL: @sql_ready
👩💻 Backend: @backend_ready
👩💻 Frontend: @code_ready
📋 IT Архив: @archive_ready
🖥 Design: @time_design
📌 Ресурсы, гайды, шпаргалки, книги и задачи для каждого языка программирования.
9 388
👩💻 Программирование теперь в Telegram!
Вот 10 обучающих каналов по самым востребованным направлениям в IT.
Выбирай своё направление:
👩💻 C/C++: @cpp_ready
👩💻 C#: @csharp_ready
👩💻 Python: @python_ready
👩💻 Java: @java_ready
📖 Общее IT: @roadmap_ready
🖥 Базы Данных & SQL: @sql_ready
👩💻 Backend: @backend_ready
👩💻 Frontend: @code_ready
📋 IT Архив: @archive_ready
🖥 Design: @time_design
📌 Ресурсы, гайды, шпаргалки, книги и задачи для каждого языка программирования.
9 388
Квиз
Мы с вами недавно коснулись темы лямбд, поэтому вдогонку устроим #quiz по этой теме. Как всегда, тут нужно либо хорошее знание стандарта, либо хорошая интуиция. Хотя интуиция поможет вам на квиз только правильно ответить, челюсть с пола она вам не поднимет, когда вы поймете, в чем дело.
Итак. Какой результат попытки компиляции(с одним флагом указания стандарта С++20) и выполнения этого кода?:
int main() {
auto test = +[]{};
test = []{};
return 0;
}
Ответ выйдет завтра.
Stay surprised. Stay cool.9 388
Что на самом деле представляют собой short circuit операторы?
Мы уже узнали, что операторы && и || для кастомных типов - простые функции. Для функций существует гарантия вычисления всех аргументов перед тем как функция начнет выполняться. Поэтому перегруженные версии этих операторов и не проявляют своих короткосхемных свойств. Однако операторы && и || для тривиальных типов - другое дело и имеют такие свойства. Но почему? Как это так работает в одном случае и не работает в другом? Давайте разбираться.
Во-первых(и в-единственных), операторы для тривиальных типов - это не функции. Они сразу превращаются в определенную последовательность машинных команд. Так как у нас теперь нет ограничения, что мы должны вычислить все аргументы сразу, то и похимичить можно уже знатно.
Если подумать, то логика тут очень похожа на вложенные условия. Если первое выражение правдиво, переходим в вычислению второго, если нет, то выходим из условия(это для &&). И если еще подумать, то у нас и нет никаких других средств это сделать, кроме джампов(условных переходов к метке). Покажу, во что примерно компиляторы С/С++ преобразуют выражение содержащее оператор &&. Не настаиваю на достоверность и точность. Объяснение больше для понимание происходящих процессов.
Вот есть у нас такой код
if (expr1 && expr2 && expr3) {
// cool operation
} else {
// even cooler operation
}
// the coolest operation
Он преобразуется примерно вот в такое:
if (!expr1) goto do_even_cooler_operation;
if (!expr2) goto do_even_cooler_operation;
if (!expr3) goto do_even_cooler_operation;
{
// cool operation
goto do_the_coolest_operation;
}
do_even_cooler_operation:
{
// even cooler operation
}
do_the_coolest_operation:
// the coolest operation
Что здесь происходит. Входим в первое условие и если оно ложное(то есть expr1 - true), то проваливаемся дальше в следующее условие и делаем так, пока наши выражения правдивые. Если они в итоге все оказались правдивыми, то мы входим в блок выполняющий клевую операцию и дальше прыгаем уже наружу первоначального условия и выполняем самую клевую операцию. Если хоть одно из выражений expr оказалось ложным, то мы переходим по метке и выполняем еще круче операцию и естественным образом переходим к выполнению самой крутой операции. Прикол здесь в трех условиях. Так как они абсолютно не связаны друг другом и последовательны, то следующее по счету выражение просто не будет выполняться, пока выполнение не дойдет до него. Таким образом и обеспечиваются последовательные вычисления слева направо.
То есть встроенные операторы && и || разворачиваются вот с такую гармошку условий. Надеюсь, для кого-то открыл глаза, как это работает)
See what's under the hood. Stay cool.
#compiler #cppcore9 388
Инфляция...
Эта старушка в последние годы конкретно так подъедала наши с вами реальные доходы. В последние 10 лет нестабильная внешняя обстановка привела к тому, что рубли обесценились минимум в 2 раза. А зарплаты так сильно не выросли.
Много способов есть, чтобы сохранить свои деньги. Но наверное самый простой на словах метод - получать зп в долларах.
Но для этого надо устраиваться в международные компании. А это совсем другой мир.
Поиск заботы за рубежом сильно отличается от российского рынка – прежде чем приступать к откликам на вакансии, нужно составить стратегию поисков, подготовить мастер-CV, Cover Letter, профиль LinkedIn... а еще подтянуть английский для интервью и заняться нетворком.
В общем, это только индуса нанимать, который будет за вас делать текущую работу. Иначе времени на это все не хватит.
Однако есть лучик света в этом темном царстве. С сервисом AgileFluent поиск работы за границей становится в разы легче. Команда из 50+ опытных экспертов в международном найме поможет на всех этапах отбора – от карьерной консультации до обсуждения оффера.
Ребята уже давно на рынке, у них в копилке уже больше 300 международных офферов для своих клиентов, среди стран – Германия, Нидерланды, Сербия, США, Испания. У них на сайте опубликовано куча успешных кейсов.
Есессно, ко всем клиентам они находят индивидуальный подход. Продукты есть на любой вкус и чек, вежливый менеджер поможет вам с выбором.
Традиционно осень – самый активный период найма в международных компаниях, так что обязательно присмотритесь к AgileFluent, если вы хотите сохранить свои деньги.
Для Вас они подготовили специальное предложение - скидка 7% на первую покупку по промокоду ПЛЮСЫ (кроме полного сопровождения и C-level) – сохраняйте и назовите его менеджеру при оформлении покупки до конца сентября.
Всегда обращайтесь к специалистам. Они помогут вам сохранить свое время, энергию и деньги по итогу.
Save your time and money. Stay cool.
Реклама. ИП Шульгина Дарья Владимировна. ИНН 221004057209.
9 388
Присвоение лямбды
#новичкам
Изучение лямбда выражений - не самая простая задача для начинающего плюсовика. Сложные термины, какие-то замыкания, списки захвата и прочее. В общем, непросто. И с виду можно подумать, что 2 одинаковые лямбды можно присваивать друг другу с легкостью. То есть может показаться, что такой код валидный:
int main() {
auto test = [](){};
test = [](){};
return 0;
}
Однако он генерирует примерно следующую ошибку:
In function ‘int main()’: error: no match for ‘operator=’ in ‘test = <lambda closure object>main()::<lambda()>{}’ note: candidate is: note: main()::<lambda()>& main()::<lambda()>::operator=(const main()::<lambda()>&) <deleted> no known conversion for argument 1 from ‘main()::<lambda()>’ to ‘const main()::<lambda()>&’Не нашел нужного оператора присваивания. Да и вообще, это ж все лямбды, почему я не могу их присваивать друг другу? Дело в том, что каждое отдельное лямбда-выражение генерирует свой тип, который называется замыканием. Только компилятор знает этот тип, вы его наружу никак не можете получить. Стандарт говорит:
The type of the lambda-expression [...] is a unique, unnamed non-union class type — called the closure type.Это легко проверить. Такой код выведет 0:
auto test = [](){};
auto test2 = [](){};
std::cout << std::is_same_v<decltype( test ), decltype( test2 )> << std::endl;
Типы действительно разные.
В целом, поэтому вместе с лямбдами всегда нужно использовать auto. Потому что ничего другого вы вместо типа физически не сможете написать. Но компилятор знает тип замыкания и сможет вывести правильный тип для переменной.
Ну и естественно, для двух рандомных классов неопределены операторы присваивания своих объектов друг другу. Поэтому код из начала и фейлится.
Differentiate thing apart. Stay cool.
#cpp119 388
Найди летающих друзей
Сегодня будет не по С++ контент, но я не мог мимо этого пройти. Надеюсь вы сейчас никуда не спешите и у вас есть огнетушитель под рукой, будет жарко.
В общем, недавно в твиттере завирусился японский "'экспресс" тест на деменцию. Задача очень простая - найти на картинке бабочку, летучую мышь и утку. Все это надо сделать за 10 мин. Успели - молодцы. Не успели - скорее всего ваша разработческая карьера продлится не так долго, как вы этого ожидаете.
У меня не хватает усидчивости на такие штуки. Через 3 минуты безрезультатного поиска мне захотелось с криками "лайт вейт бэйбэээ" выкинуть что-нибудь тяжелое из окна и я понял, что пора залезать в комменты и ловить спойлеры. Буду верить, что раз я искал не 10 мин, это все не считается.
❤️ - нашел всех за 10 мин.
🤬 - где эта ср*ная бабочка?!
Keep calm. Stay cool.
#fun
9 388
nullptr
#новичкам
Вероятно, каждый, кто писал код на C++03, имел удовольствие использовать NULL и постоянно ударяться мизинцем ноги об этот острый уголок тумбочки. Дело в том, что NULL использовался, как обозначение нулевого указателя, который никуда не указывает. Но если он для этого использовался - это не значит, что он таковым и являлся. Это макрос, который мог быть определен как
0 aka int zero или 0L aka zero long int, но всегда это вариация интегрального нуля. И уже эти чиселки могли быть приведены к типу указателя.
Вот в этом-то и вся проблема. NULL очень явно хочет себя видеть в виде указателя, но по факту в зеркале видит число. Допустим, у нас есть 2 перегрузки одной функции: одна для инта, вторая для указателя:
class Spell { ... };
void castSpell(Spell * theSpell);
void castSpell(int spellID);
int main() {
castSpell(NULL);
}
Намерения ясны: мы хотим вызвать перегрузку для указателя. Но это гарантировано не произойдет! В произойдет один из двух сценариев: если NULL определен как 0, то просто без объявления войны в 4 часа утра вызовется вторая перегрузка. Если как 0L, то компилятор поругается на неоднозначный вызов: 0L может быть одинаково хорошо сконвертирован и в инт, и в указатель.
Проблему можно решить енамами и передавать для нулевого spellID что-то типа NoSpell. Но надо опять городить огород. Почему все не работает из коробки?!
С приходом С++11 начало работать из коробки. Надо только забыть про NULL и использовать nullptr.
Ключевое слово nullptr обозначает литерал указателя. Это prvalue типа std::nullptr_t. И nullptr неявно приводится к нулевому значению указателя для любого типа указателя. Это объект отдельного типа, который теперь к простому инту не приводится.
Поэтому сейчас этот код отработает как надо:
class Spell { ... };
void castSpell(Spell* theSpell);
void castSpell(int spellID);
int main() {
castSpell(nullptr);
}
Так как nullptr - значение конкретного типа std::nullptr_t, то мы может принимать в функции непосредственно этот тип, а не общий тип указателя. Такая штука используется, например, в реализации std::function, конструктор которого имеет перегрузку для std::nullptr_t и делает тоже самое, что и конструктор без аргументов.
/**
* @brief Default construct creates an empty function call wrapper.
* @post !(bool)this
/
function() noexcept
: _Function_base() { }
/
* @brief Creates an empty function call wrapper.
* @post @c !(bool)*this
/
function(nullptr_t) noexcept
: _Function_base() { }
По той же причине nullptr даже при возврате через функцию может быть приведен к типу указателя. А вот обычные null pointer константны не могут похвастаться таким свойством. Они могут приводиться к указателям только в виде литералов.
template<class T>
constexpr T clone(const T& t)
{
return t;
}
void g(int *)
{
std::cout << "Function g called\n";
}
int main()
{
g(nullptr); // Fine
g(NULL) // Fine
g(0); // Fine
g(clone(nullptr)); // Fine
// g(clone(NULL)); // ERROR: non-literal zero cannot be a null pointer constant
// g(clone(0)); // ERROR: non-literal zero cannot be a null pointer constant
}
clone(nullptr) вернет тот же nullptr и все будет работать гладко. А для 0 и NULL функция вернет просто int, который сам по себе неявно не конвертится в указатель.
Думаю, что вы все и так пользуете nullptr, но этот пост обязан быть на канале.
Как говорит одна древняя мудрость: "Use nullptr instead of NULL, 0 or any other null pointer constant, wherever you need a generic null pointer."
Be a separate subject. Stay cool.
#cppcore #cpp119 388
Зачем может понадобиться делать деструктор приватным?
#новичкам
Недавно мы узнали, что можно делать деструктор приватным. Те, кто не слышал о этой технике, скорее всего подумали: "Сомнительно........но.....окэй". Потому что с первого взгляда не очень понятно, для чего вообще нужно заниматься этими непристойностями in the first place. Чтобы запретить пользователю создавать объекты на стеке и в статической области?
Ну и действительно, этот паттерн используется нечасто. Однако кейсы применения все же есть.
Один из основных - ATL COM объекты. Их цикл жизни регулируется с помощью подсчета ссылок. И чтобы никто не смог удалить объект, на который все еще ссылаются ссылки, для объектов вводят приватный деструктор и метод Release. В нем происходит декремент ссылки, проверка их количества и удаление объекта, если он остался больше никому не нужным. Выглядеть это может примерно так:
int MyRefCountedObject::Release()
{
_refCount--;
if ( 0 == _refCount )
{
delete this;
return 0;
}
return _refCount;
}
Как можно было понять, это довольно устаревшая и нишевая виндовая история. Раньше паттерн подсчета ссылок внутри самого класса был более популярен. Теперь, в эпоху стандартных умных указателей, это скорее странное легаси.
Помимо этого примера, для меня вообще довольно странная история, чтобы позволять какому-то коду создавать объект, но не позволять удалять его. Если я бы захотел контролировать удаление объекта, то с вероятностью 99% я бы захотел контролировать его создание. Чтобы так сказать стать черным властелином его цикла жизни. Поэтому, на мой взгляд, приватный деструктор должен идти вместе с приватным конструктором.
Для синглтонов имеет смысл сделать деструктор приватным, чтобы никто по ошибке не смог явно вызвать на ссылке деструктор. Сам деструктор вызывается в статическом методе-геттере, который имеет доступ к приватным полям, тут все аккуратно работает.
Также приватные конструктор и деструктор могут идти в комплекте с двумя статическими методами: Create() и Destroy().
Например, из Create возвращать умный указатель и указывать в качестве его делитера приватный статический метод Destroy. Таким образом вы можете реализовать довольно сложную логику создания и удаления объекта. Когда, например, у вас есть конструктор и метод init, то для ограничения работы с созданным, но не инициализированным объектом, используется статический метод Create. По аналогии для деструктора и finalize мы используем Destroy. Мы никого не ограничиваем в правильном создании и удалении объектов. Просто хотим, чтобы только через эти два метода проходило создание и удаление объектов.
class ControlLifeCycle
{
public:
static std::unique_ptr<ControlLifeCycle, void()(ControlLifeCycle)> Create() // Factory
{
auto deleter = [](ControlLifeCycle * obj)
{
ControlLifeCycle::Destroy(obj);
};
std::cout << "I was born!" << std::endl;
return std::unique_ptr<ControlLifeCycle, decltype(deleter)>(new ControlLifeCycle, deleter);
}
private:
static void Destroy(ControlLifeCycle* ptr)
{
std::cout << "I've been destroyed!" << std::endl;
delete ptr;
}
ControlLifeCycle() {} // Private CTOR and DTOR
~ControlLifeCycle() {}
};
ControlLifeCycle global_var; // error: ctor and dtor are private
int main ()
{
ControlLifeCycle stack_var; // error: ctor and dtor are private
ControlLifeCycle* dynamic_var = new ControlLifeCycle; // error: private ctor
auto smart_var = ControlLifeCycle::Create(); // OK
}
Можно из метода Create возвращать просто указатель и оставить метод Destroy публичным. Тогда только мы своими очумелыми ручками сможем напечатать в нужном месте кода "Class::Destroy(ptr);" и только тогда произойдет освобождение ресурсов и ни в каком другом случае.
В общем, эта техника может использоваться в любом месте, где нам необходим полный контроль над циклом жизни объекта и над способом его рождения и захоронения.
Control cycle of your life. Stay cool.9 388
ИБэшечка
Ребятки, давайте на чистоту. Программирование - это конечно круто. Условия, циклы, архитектура, базы данных - все это интересно и весело. Но на этом далеко не уедешь. Самые востребованные специалисты сейчас - это не те люди, которые знают последний стандарт. Это конечно важно. Но настоящие чеки зашибают люди, которые шарят за свою предметную область.
Количество информации сейчас растет как живот моего бати после 40-ка лет. Этот процесс уже не остановить. Но можно оседлать этого безудержного цифрового скакуна. Спросите как? А я вам отвечу.
Любую информацию надо защищать. И чем ее больше, тем больше защитников для нее требуется. ИБ - это настоящее и будущее.
Поэтому ловите два канала на тему ИБ и хакинга
ZeroDay - Уроки по кибербезопасности и хакингу с нуля. Вирусы, взломы, OSINT, криптография и свежие новости. Там даже кошка научится разворачивать китайский файервол.
Белый Хакер - программное обеспечение, утилиты, OSINT, инструменты, полезная литература и много другое. Совершенно новый формат непохожий на другие каналы. Можно и над мемасами покекать, и взламывать соседский вай-фай научиться.
Входим в будущее подготовленными
متاح الآن! بحث تيليغرام 2025 — أهم رؤى العام 
