cookie

ما از کوکی‌ها برای بهبود تجربه مرور شما استفاده می‌کنیم. با کلیک کردن بر روی «پذیرش همه»، شما با استفاده از کوکی‌ها موافقت می‌کنید.

avatar

Грокаем C++

Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов. По всем вопросам - @ninjatelegramm Менеджер: @Spiral_Yuri Реклама: https://telega.in/c/grokaemcpp Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat

نمایش بیشتر
پست‌های تبلیغاتی
5 068
مشترکین
+424 ساعت
+177 روز
+1 19230 روز

در حال بارگیری داده...

معدل نمو المشتركين

در حال بارگیری داده...

​​Вектор ссылок #опытным Не знаю, задумывались ли вы когда-нибудь создать вектор ссылок. Наверное задумывались, но не прям, чтобы пытались воплотить в жизнь. Не очень понятны кейсы применения этих сущностей. Однако они довольно хорошо подсвечивают одну интересную и базовую особенность вектора. Дело в том, что вы не можете создать вектор ссылок. Не можете и все. Попробуйте написать что-то такое и запустить сборку:
std::vector<int&> vec;
Вылезет какая-то совершенно монструозная кракозябра, по которой мы хрен пойми, что должны понять. Это немного камней в огород бесполезных сообщений об ошибках в плюсах, но продолжим. В сущности это происходит по одной причине. Шаблонный тип vec не удовлетворяет требованиям к типам элементов вектора. До C++11 и появления мув-семантики элементы вектора должны были удовлетворять требованиям CopyAssignable и CopyConstructible. То есть из этих объектов должны получаться валидные копии, притом что исходный объект оказывается нетронутым. Это условие, кстати, не выполняется для запрещенного в РФ иноагента std::auto_ptr. Так вот ссылочный тип - не CopyAssignable. При попытке присвоить ссылке что-то копирования не происходит, а происходит просто перенаправление ссылки на другой объект. После С++11 требования немного смягчились и теперь единственный критерий, которому тип элементов вектора должен удовлетворять - Erasable. Но ссылки также не попадают под этот критерий(для них не определен деструктор). Поэтому сидим без вектора ссылок. Или нет? Можно хакнуть этот ваш сиплюсплюс и создать вектор из std::reference_wrapper. Это такая тривиальная обертка над ссылками, чтобы ими можно было оперировать, как обычными объектами. В смысле наличия у них всех специальных методов классов. Но будьте осторожны(!), потому что есть одна большая проблема со ссылками. Вот мы создали и заполнили контейнер ссылками на какие-то объекты. И потом вышли из скоупа, где были объявлены объекты, на которые ссылки указывают. Вектор есть, ссылки есть, а объектов нет. Это чистой воды undefined behavior. Ссылки будут указывать на уже удаленные объекты. Пример:
std::vector<std::reference_wrapper<int>> vec;
int * p = nullptr;
{
  int i;
  for (i = 0, p = &i; i < 5; i++) {
    vec.emplace_back(i);
  }
}

*p = 10;

for (int i = 0; i < 5; i++) {
  std::cout << vec[i] << std::endl;
}
Вывод будет такой:
10
10
10
10
10
Подумайте пару секунд, почему так. Переменная i меняется и мы добавляем ссылки на эту переменную в вектор. По итогу все элементы вектора указывают на одну и ту же переменную. Поэтому и элементы все одинаковы. Но раз ссылка - это обертка над указателем, то элементы вектора по факту хранят адрес того места, где была переменная i. Поэтому все изменения ячейки памяти этой переменной будут отражаться на ссылках, даже если переменная уже удалена. Вот мы и сделали грязь: сохранили адрес ячейки и изменили его после выхода из скоупа цикла и удаления переменной i. Так обычно и происходит на стеке: переменная кладется на стек, с ней работают, она удаляется при выходе из скоупа и потом другие объект занимают место удаленной переменной в памяти. Мы здесь сымитировали такой процесс. Так как вектор после выхода из скоупа цикла хранит висячие ссылки, то поведение в такой ситуации неопределено и наш грязный мув четко это показывает. После присваивания нового значения по указателю p все ссылки будут иметь то же самое значение. Хотя изначально такая ситуация вообще не предполагалась. Будьте аккуратны со ссылками. В этом случае проще использовать какой-нибудь умный указатель. Все будет чинно и цивильно. И никакого UB. Be careful. Stay cool. #cpp11 #cppcore #STL
نمایش همه...

🔥 5👍 3 1
​​Swap idiom. Pros and cons #опытным В этом посте поговорили про суть swap идиомы. Сегодня обсудим ее плюсы и минусы. Плюсы вроде как обсуждали, но я финализирую, когда можно рассмотреть внедрение swap idiom: ✅ Если у вас конструктор копирования может бросить исключение и вы можете написать небросающую функцию swap. Тогда за счет того, что захват ресурсов(копирование или перемещение во временный объект параметра функции) происходит до модификации текущего объекта, то мы получаем строгую гарантию безопасности исключений при работе с присваиванием объектов. ✅ Если вы хотите красивый, лаконичный и понятный код без повторений действий. ✅ Вы не очень беспокоитесь о потенциальных потерях производительности. Погнали по минусам: ❗️ Не всегда можно написать nothrowing swap. Для базовых типов и указателей - да. Но swap нетривиальных типов использует временный объект. При создании которого и может возникнуть исключение. Сейчас swap делается с помощью перемещающих операций, но например в С++03 std::string мог кинуть исключение в копирующем конструкторе. Да и сейчас поля класса могут быть немувабельными и бросающими при копировании. Это надо иметь ввиду. ❗️ Каждый раз при присваивании мы выполняем 2 операции: конструктор копирования + swap или конструктор перемещения + swap. "Потери производительности" надо конечно тестить и смотреть реальные результаты, но в голове все равно надо держать потенциальные просадки. ❗️ Самостоятельно писать деструктор для менеджинга ресурсов в 2к24 - такая себе практика в большинстве случаев. Давно есть std::unique_ptr<T[]>, указатели с кастомными делитерами и прочие вещи. Одно из ключевых преимуществ идиомы - сокращение и переиспользование кода. Так вот с отсутствием деструктора вам вообще может не понадобится кастомное присваивание и вы сможете объявить операции дефолтными, поэтому надобность в идиоме сама по себе отпадет. ❗️❗️ Часто пропускаемый огромный минус: технически у нас есть оператор перемещения, который может принимать rvalue ссылки. Однако мы явным образом не реальзовывали присваивание перемещением, поэтому по правилу 5, компилятор не будет его генерировать за нас и у класса просто будет отсутствовать оператор присваивания перемещением. И хоть текущий класс мы можем мэнэджить без присваивания перемещением, то ситуация изменится, когда мы сделаем текущий класс полем другого. Тогда у этого другого класса не будет генерироваться дефолтный оператор присваивания перемещением! Для его генерации все поля должны иметь такие операторы. А в нашем классе его нет. Это значит, что по дефолту будет использоваться копирующее присваивания и все остальные поля нового класса будут копироваться. А вы об этом даже не знали! И получили жесткую просадку и, потенциально, некорректную логику.
struct FirstField {
  FirstField() = default;
  FirstField(const FirstField& other) {
    std::cout << "FirstField Copy ctor" << std::endl;
  }
  FirstField& operator=(FirstField other) {
    std::cout << "FirstField assign" << std::endl;
    return *this;
  }
  FirstField(FirstField&& other) {
    std::cout << "FirstField Move ctor" << std::endl;
  }
};

struct SecondField {
  SecondField() = default;
  SecondField(const SecondField& other) {
    std::cout << "SecondField Copy ctor" << std::endl;
  }
  SecondField& operator=(const SecondField& other) {
    std::cout << "SecondField Copy assign" << std::endl;
    return *this;
  }
  SecondField(SecondField&& other) {
    std::cout << "SecondField Move ctor" << std::endl;
  }
  SecondField& operator=(SecondField&& other) {
    std::cout << "SecondField Copy assign" << std::endl;
    return *this;
  }
};

struct Wrapper {
  FirstField ff;
  SecondField sf;
};

Wrapper w;
w = std::move(Wrapper{});

// OUTPUT:
// FirstField Move ctor
// FirstField assign
// SecondField Copy assign
Выбор использовать или не исопльзовать - как всегда за вам. Тестируйте гипотезы и выбирайте из них лучшую. Analyse your solutions. Stay cool. #cppcore #cpp11
نمایش همه...

👍 12 3🔥 3
Repost from N/a
Photo unavailableShow in Telegram
Привет! Меня зовут Бекхан, мне 28 лет. Узнайте обо мне больше, открыв картинку над постом или прочитав полный текст здесь. Сейчас я занимаюсь разработкой собственной игры с нуля и сталкиваюсь с различными вызовами и подводными камнями. Все свои знания и опыт я конспектирую и делюсь ими на своем сайте и телеграм-канале. Я всегда стараюсь глубоко и основательно разбираться в возникающих вопросах, и мне кажется, что это будет полезно и для вас. Хотя постов в моем телеграм-канале пока не так много, я уверен, что с увеличением аудитории у меня будет больше мотивации делиться своим опытом и писать новые посты. Подписывайтесь на мой телеграм-канал Bekhan Code, чтобы не пропустить полезные советы и инсайты по разработке игр. Попасть в Bekhan Code
نمایش همه...
👎 10👍 9🔥 3 2😁 2 1
Ответ Буду описывать мои мысли, которые мне приходили в голову при решении. Самое базовое, что надо понимать: если есть всего одно яйцо, то стратегия всегда одна - идти снизу вверх и подряд с каждого этажа скидывать, пока яйцо не разобьется. Число 2 как бы намекает на то, что нужно что-то уполовинить. Но если мы сбросим с 50-го этажа и яйцо разобъется, это нам не сильно сократит задачу - придется в худшем случае еще 49 раз бросить. Но можно шаги по-другому уполовинить - ходить через один этаж. Как только первое яйцо разобьется, можно пойти на этаж ниже и сбросить оттуда второе яйцо. Так мы точно определим нужный этаж. В этом случае мы гарантировано найдем нужный этаж за 51 бросок. Той же логикой можно увеличивать шаг - идти через 3/4/5 и тд этажей. Тогда после того, как первое яйцо разобьется, мы пойдем с предыдущего посещенного этажа вверх и будем подряд бросать. Формула для нахождения гарантированного количества шагов c помощью такого методв - (100 // step) + (step - 1). Путем нехитрых математико-алгебраических вычислений придем к тому, что оптимальный вариант - идти через 10 этажей. Да и число красивое. В этом случае мы сможем найти нужный этаж за 19 бросков. Но оптимальное ли это решение? Вообще говоря, нам очень нравится ходить через много этажей. Но не нравится потом много раз бросать после первого разбитого яйца. Если не привязываться к гарантированности, то для малых N нам выгодно ходить с большим шагом. Потому что на поиски рэнджа этажей нам потребуется немного бросков. И этот фактор становится все менее важным, с увеличением N. Можно было бы как-то соединить: ходить в начале с большим шагом, но с каждым броском первого яйца его уменьшать, пока нам вообще второе яйцо не понадобится или пока не наступит 100 этаж. Так можно и соединить. Давайте с каждым броском первого яйца уменьшать шаг на 1 этаж. И к сотому этажу пусть у нас шаг уменьшится до минимума. Получается, что нам нужно начинать с шага в 14. После того, как бросили с 14-го, идем на 27. Потом на 39. И так далее. Прикол в чем - нам всегда нужно будет максимум 14 бросков для нахождения нужного этажа. Это и является ответом. А я говорил, что задача красивая) Solve your problems. Stay cool.
نمایش همه...
👍 20🔥 7 5 1
Photo unavailableShow in Telegram
Разбей пару яиц Попалась на глаза интересная логическая задача, решил с вами тут поделиться. Мне понравилось, что в ней нет какого-то твиста или секретного приема, не зная которые до ответа не дойти. Все решается очень плавно, даже как будто бы итеративно. Условие: у вас есть 2 яйца и 100 этажный дом. Яйца у вас очень крепкие(мы же плюсовики), поэтому если их сбрасывать из окон этого дома, то они не будут биться. Однако рано или поздно физика победит и, начиная с какого-то номера этажа N, яйца все-таки будут разбиваться. Вы можете перемещаться в доме вверх и вниз в любой последовательности. Какое минимальное количество бросков гарантировано понадобится, чтобы установить нужный этаж под номером N? Знакомых с решением, прошу воздержаться от комментариев. Всех остальных призываю к обсуждению решения. Уверен, оно вам понравится своей красотой) Ответ будет, как всегда вечером. Хватит яйца мять, пора их разбивать! Challenge yourself. Stay cool. #задачки
نمایش همه...
🔥 11👍 3 1 1👏 1
​​Swap idiom Рассуждения в комментах под предыдущим постом навели меня на мысли рассказать о swap idiom. Дело в том, что, когда у вас есть рабочие деструктор, конструктор копирования и перемещения, вы можете соединять методы, которые должны принимать константную lvalue ссылку и rvalue ссылку, в один метод, который принимает параметр по значению. То есть можно вместо 2-х методов сеттеров можно написать 1:
template <class T>
struct TemplateClass {
  void SetValue(T value) {
    value_ = std::move(value);
  }
private:
  T value_;
};
Этой же концепцией вдохновлено появление swap идиомы. На самом деле я немного вру, но с появлением мув-семантики идиома приобрела эти черты. Суть в чем. Есть у вас класс, который мэнэджит какие-то ресурсы. Например самописный класс массива:
class SimpleArray
{
public:
    SimpleArray(std::size_t size = 0)
        : mSize(size),
          mArray(mSize ? new intmSize : nullptr) {}

    SimpleArray(const SimpleArray& other)
        : mSize(other.mSize),
          mArray(mSize ? new int[mSize] : nullptr) {
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }
  SimpleArray(simple_array&& other) noexcept
        : mSize(other.mSize),
          mArray(other.mArray) {}

    ~SimpleArray()
    {
        delete [] mArray;
    }

private:
    std::size_t mSize;
    int* mArray;
};
Все хорошо, но для выполнения правила 5 нам нужно определить еще и 2 оператора присваивания: перемещающий и копирующий. Обычно в них в начале очищают существующий объект и потом записываются новые данные. Покажу на примере конструктора копирования:
SimpleArray& operator=(const SimpleArray& other) {
    if (this != &other) {
        delete [] mArray;
        mArray = nullptr;
        mSize = 0;

        mSize = other.mSize;
        mArray = mSize ? new int[mSize] : nullptr;
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }
    return *this;
}
В такой реализации есть 3 проблемы: ❗️ Нам просто необходима проверка на самоприсвоение, чтобы в объекте остались те же данные. Но это настолько редкий кейс, что каждый раз при присвоении тратить время на проверку не очень хочется. А хочется операторы без этой проверки. ❗️ У нас есть только базовая гарантия исключений. Если из new бросится исключение, то состояние изменяемого объекта хоть и останется согласованным, но оно все равно изменится. А операция не завершится до конца. Хотелось бы строгой гарантии безопасности исключений. ❗️ Мы повторяем код. Помимо проверки самоприсваивания и очищения ресурсов тупо повторяется код копирующего конструктора. Хочется этого не делать. Чтобы решить эти проблемы, мы можем сделать интересную штуку - принимать параметр оператора присваивания на обычное значение. Тогда на входе оператора у нас уже будет готовый скопированный(или перемещенный объект) и нам нужно будет лишь поменять содержимое этих двух объектов местами. И нам не нужно беспокоиться о том, что останется в параметре функции - он все равно удалится после выхода из нее. Теперь оператор будет выглядеть так:
SimpleArray& operator=(SimpleArray other) noexcept {
  swap(*this, other);
  return *this;
}
Как же красиво! Нам осталось только реализовать функцию swap. Она может быть и методом класса, но почему бы еще не иметь просто функцию, которая свапает контент. Поэтому покажу реализацию дружественной функции.
friend void swap(SimpleArray& first, SimpleArray& second) noexcept {
  using std::swap;
  swap(first.mSize, second.mSize);
  swap(first.mArray, second.mArray);
}
Выглядит кратко, читаемо, да еще и исключений нет(об этом даже явно в коде можно сказать)! Ляпота. ПРОДОЛЖЕНИЕ В КОММЕНТАРИЯХ Stay laconic. Stay cool. #patter #cppcore #cpp11
نمایش همه...

🔥 17👍 4 4😁 1
Photo unavailableShow in Telegram
🔥 Программистика находка для каждого питониста Проведём за ручку от самых простых проектов до сложных. Расскажем самые трудные концепции простым языком. Научим не боятся сложных задач и наслаждаться своей работой 👍Подписывайся и развивайся
نمایش همه...
🔥 2 1👍 1 1🤬 1🫡 1
​​Шаблонный сеттер #опытным Увидел на ревью интересный кейс. Мы о нем уже говорили, что не сильно акцентировали внимание. Сегодня больше времени уделим одному интересному явлению. Если у вас есть какой-то шаблонный класс, который хранит тип Т, и в этом классе есть сеттер на этот тип, то по давней привычке(еще с 98 стандарта) его можно написать вот так:
template <class T>
struct TemplateClass {
  void SetValue(const T& value) {
    value_ = value;
  }
private:
  T value_;
};
Привычка - дело хорошее и экономит ресурс мозга на выполнение действий. Не так много когнитивного внимания нужно тратить на деятельность. Но иногда привычки ограничивают нас. Мы-то уже в modern C++ эре. И в данном случае как раз такой кейс. Что будет, если мы захотим передать в этот метод временный объект? Например так:
struct ShowConstruct {
  ShowConstruct() = default;
  ShowConstruct(int value) : field{value} {
    std::cout << "Param construct " << field << std::endl;}
  ShowConstruct& operator=(const ShowConstruct& other) {
    field = other.field;
    std::cout << "Copy assign " << field << std::endl;
    return *this;}
  ShowConstruct& operator=(ShowConstruct&& other) {
    field = other.field;
    std::cout << "Move assign " << field << std::endl;
    return *this;}
  int field = 0;
};

TemplateClass<ShowConstruct> obj;
obj.SetValue(ShowConstruct{5});
На экран выведется:
Param construct 5
Copy assign 5
Это значит, что даже если мы передаем в такой сеттер временный объект, у которого можно забрать его ресурсы и сэкономить на копировании, мы все равно не получаем этих бенефитов. Потому что в сеттере value уже относится к категории lvalue. А при присваивании объекта от lvalue будет вызываться копирующий оператор присваивания. А нам бы хотелось, чтобы вызывался перемещающий оператор. Как этого достичь? Использовать универсальную ссылку. Скажет прошаренный читатель. Для шаблонного кода мы можем пометить параметр метода двумя амперсандами и дальше внутри передавать его во все места через std::forward. Таким образом, если нам на вход пришел именованный объект, то std::forward скастует его к lvalue ссылке, а если временный, то к rvalue ссылке. И это поможет нам в нужных случая вызывать правильный оператор присваивания. И std::forward и universal reference доступны с 11-го стандарта вместе с введением мув-семантики.
template <class T>
struct TemplateClass {
  void SetValue(T&& value) {
    value_ = std::forward<T>(value);
  }
private:
  T value_;
};

TemplateClass<ShowConstruct> obj;
obj.SetValue(ShowConstruct{5});
Теперь мы получаем нужный вывод:
Param construct 5
Move assign 5
Однако этот прошаренный читатель оказался не таким уж и прошаренным! Такая штука не сработает для шаблонных параметров класса!
in class template argument deduction, template parameter 
of a class template is never a forwarding reference
Универсальная ссылка(она же forwarding reference) появляется только, когда тип параметра функции Т&& и Т - шаблонной параметр самой функции. В нашем случае нет никакого вывода - тип Т известен из класса. Поэтому и никакой универсальной ссылки не появляется. Мы просто определили метод, который принимает rvalue ссылку. При попытке передать туда lvalue будет ошибка:
TemplateClass<ShowConstruct> obj;
ShowConstruct lvalue{7};
obj.SetValue(lvalue);

//ERROR: rvalue reference to type 'ShowConstruct' 
// cannot bind to lvalue of type 'ShowConstruct'
Какой выход? Просто рядышком с сеттером для константной lvalue ссылки написать сеттер для rvalue ссылки.
template <class T>
struct TemplateClass {
  void SetValue(const T& value) {
    value_ = value;
  }
  void SetValue(T&& value) {
    value_ = std::move(value);
  }
private:
  T value_;
};

TemplateClass<ShowConstruct> obj;
obj.SetValue(ShowConstruct{5});
ShowConstruct lvalue{7};
obj.SetValue(lvalue);
Тогда все нормально скомпилируется и в нужных места будут вызваны нужные операторы. Stay universal. Stay cool. #cpp11
نمایش همه...

🔥 28👍 6 4 2
​​Правила константности #новичкам Константность - важное свойство сущности в коде. Оно не только позволяет обезопасить объекты от изменения, но еще и говорит программисту о гарантиях, которые дает та или иная функция. Допустим, принимая параметр по константной ссылке, функция говорит программисту: "расслабься, ничего я не сделаю с твоим объектом". Это повышает читаемость кода. В С++ много чего можно сделать константным. Объекты, указатели, ссылки, параметры функции, методы класса и тд. И зачастую новичкам сложно разобраться в правилах присваивания константности. Сегодня разберемся в этом. Не будем долго задерживаться над константными методами. Константные объекты могут вызвать только константные методы. Все. Синтаксис такой:
void Class::Method(Type1 param1, Type2 param2) const {}
Теперь и константные, и неконстантные объекты могут вызывать метод Method. Дальше все так или иначе сводится к правилам в объявлении переменных. Что в качестве поля класса, параметра функции, что объявлении обычной переменной - разницы нет. Правила одни. Поехали. Константный объект можно объявить двумя способами:
const T obj;
// Либо
T const obj;
Эти записи абсолютно эквивалентны! Это очень важно запомнить, потому что при разговоре о ссылках и указателях это играет большую роль. Собственно также есть 2 нотации объявления массивов констант:
const T arr[5];
// либо
T const arr[5];
И 2 нотации определения ссылок:
const T& ref;
// либо
T const & ref;
Помните, что при создании ссылки в скоупе функции вы обязаны ее инициализировать. При объявлении поля класса этого делать не обязательно, потому что вы не создаете объект прямо сейчас. Но вы обязаны инициализировать ссылку до входа в конструктор либо через список инициализации конструктора, либо через default member initializer, так как базового поля класса иницализируются до входа в конструктор. При объявлении параметра функции тоже не нужно сразу инициализировать ссылку, потому что функция принимает уже существующую и инициализированую ссылку на вход. Обычно при таком объявлении ссылку называют константной. Это не совсем верно. Ссылка при любых обстоятельствах сама по себе является константной. Как только вы забиндили ссылку на объект, она всегда будет смотреть на этот объект и изменять его. Более подробно про особенности ссылок посмотреть тут. При новом присваивании ссылки вызовется оператор присваивания и изменится существующий объект.
struct Type {
  Type& operator=(const Type& other) { 
    std::cout << "copy assign" << std::endl; 
    return *this;
  }
};

Type a{};
Type& b = a;
b = Type{};
// OUTPUT:
// copy assign
Когда говорят "константная ссылка" имеют ввиду ссылку на константу. И при любом виде объявления const T& ref или T const & ref она также будет ссылкой на константу. Теперь указатели. Наверное самое сложное из всего перечисленного. Указатели, в отличии от ссылок, сами могут быть константными, да еще и указывать на константные объекты. А еще могут быть многоуровненые указатели. В общем сложно. Но есть правило: при объявлении указателя каждое появление ключевого слова const относится к тому уровню вложенности, который находится слева от этого слова. Вы просто читаете объявление справа налево и получаете правильное понимание объявления. Примеры:
// Читаем справа налево
int * const ptr; // ptr - это константный указатель на инт
int * * const ptr; // ptr - это константный указатель на указатель на инт
int * const * const ptr; // ptr - это константный указатель на константный указатель на инт

// Самый низкий уровень, который относится к самому объекту, 
// можно писать двумя способами, о которых мы говорили выше

int const * const * * const ptr; // ptr - это константный указатель на указатель 
                                 // на константный указатель на интовую константу
const int * const * * const ptr; // Тоже самое
ПРОДОЛЖЕНИЕ В КОММЕНТАРИЯХ Rely on fixed thing in your life. Stay cool. #cppcore ПРОДОЛЖЕНИЕ В КОММЕНТАРИЯХ
نمایش همه...

👍 16 5🔥 5😁 3
​​Опасности использования директив препроцессора Вчерашний способ выбора ветки кода имеет несколько недостатков: ⛔️ Препроцессор работает с буквами/текстом программы, но не понимает программных сущностей. Это значит, что типабезопасность уходит из окна, и открывается простор для разного рода трудноотловимых багов. ⛔️ При компиляции проверяется только та ветка, которая попадет в итоговый код. Если вы не протестировали свой код для разных значений внешних параметров, а такое бывает например когда пока что есть только одно значение, а другое будет только в будущем, а в будущем тесты естественно никто дописывать не будет. Тогда в этом будущем, когда значение параметра неожиданно изменится, в лучшем случае упадет сборка, а в худшем - это будет работать на проде и чудить одному Богу известные чудеса. ⛔️ Вы ограничены возможностями препроцессора. Это значит, что вы не можете использовать в условии compile-time вычисления (аля результат работы constexpr функции). ⛔️ Отсюда же вытекает отсутствие возможности проверки условий, основанных на шаблонных параметрах кода. Это все из-за того, что препроцессор работает до начала компиляции программы. Он в душе не знает, что вы вообще программу пишите. Ему в целом ничего не мешает обработать текст Войны и Мира. Именно из-за отсутствия понимания контекста программы, мы и не можем проверять условия, основанные на compile-time значениях или шаблонных параметрах. Если вы хотите проверить, указатель ли к вам пришел в функцию или нет, или собрать какую-то метрику с constexpr массива и на ее основе принять решение - у вас ничего не выйдет. ⛔️ Вы очень сильно ограничены возможностями препроцессора. Попробуйте например сравнить какой-нибудь макрос с фиксированной строкой. Спойлер: у вас скорее всего ничего не выйдет. Например, как в примере из поста выше мы не можем написать так:
int DotProduct(const std::vector<int>& vec1, const std::vector<int>& vec2)
{
  int result = 0;
  #if CPU_TYPE == "INTEL"
    // mmx|sse|avx code
  #elif CPU_TYPE == "ARM"
    // arm neon code
  #else 
    static_assert(0, "NO CPU_TYPE IS SPECIFIED");
  #endif
  return result;
}
Поэтому и приходилось определять тип циферками. Это конечно мем: сущность, которая работает с текстом программы, то есть со строками, не может работать со строками. ⛔️ С препроцессором в принципе опасно работать и еще труднее отлаживать магические баги. Могут возникнуть например вот такие трудноотловимые ошибки. Вам придется смотреть уже обработанную единицу трансляции, причем иногда даже не понимая, где может быть проблема. А со всеми включенными бинарниками и преобразованиями препроцессора это делать очень долго и больно. А потом оказывается, что какой-то умник заменил в макросах функцию DontWorryBeHappy на ILovePainGiveMeMore. В комментах @xiran22 скидывал пример библиотечки, написанной с помощью макросов. Вот она, можете посмотреть. Это не только пример сложности понимания кода и всех проблем выше. Тут просто плохая архитектура, затыки которой решаются макросами. Поделитесь в комментах своими интересными кейсами простреленных ступней из-за макросов. Avoid dangerous tools. Stay cool. #compiler #cppcore
نمایش همه...

🔥 18👍 4 2 2😁 1
یک طرح متفاوت انتخاب کنید

طرح فعلی شما تنها برای 5 کانال تجزیه و تحلیل را مجاز می کند. برای بیشتر، لطفا یک طرح دیگر انتخاب کنید.