Во что выведется тип Т?
Anonymous Poll
6%
int
6%
int&
14%
std::vector<int>
13%
const std::vector<int>
42%
const std::vector<int>&
20%
Будет ошибка компиляции
Объяснение
Пойдем по порядку
Снова проверка на универсальную ссылку. Только в формате Т&& параметр шаблонной функции может называться универсальной ссылкой. Здесь такого нет.
Мы передает обычный lvalue в функцию, оно не сможет кастануться к rvalue reference, поэтому будет ошибка компиляции.
Интересный случай. Потому что мы все знаем приколы с конструкторами вектора, и их стремлению интерпретировать набор объектов внутри фигурных скобок как std::initializer_list. Я даже назвал функцию vector, чтобы вас надурить немного. Да и по правилам вывода типов auto, {1, 2, 3} тоже выведется в std::initializer_list. Но вывод в шаблонах - это другое. Компилятор не может правильно интерпретировать, что за сущность мы пытаемся засунуть в функцию, поэтому откажется это компилировать.
Вроде как должно быть int, но если внимательно присмотреться к сигнатуре функции, то можно рассмотреть, что она принимает неконстантную ссылку. А временный объект, который мы в нее передаем, не сможет привестить к неконстантной ссылке. Поэтому снова будет ошибка компиляции.
Нет, здесь не будет ошибки компиляции. Слишком много уловок - нехорошо. Тут просто хороший кейс на проверку полноты усвоенного материала. Здесь param - универсальная ссылка. Передаем мы в функцию lvalue, а это значит, что тип Т точно будет ссылкой. Вопрос на что. Здесь никакой вложенности нет, поэтому ничего от типа
Check your knowledge. Stay cool.
Пойдем по порядку
template <class T>
void func(std::vector<T> && param) {}
std::vector<int> vec;
func(vec);
Снова проверка на универсальную ссылку. Только в формате Т&& параметр шаблонной функции может называться универсальной ссылкой. Здесь такого нет.
Мы передает обычный lvalue в функцию, оно не сможет кастануться к rvalue reference, поэтому будет ошибка компиляции.
template <class T>
void vector(const T & param) {}
vector({1, 2, 3});
Интересный случай. Потому что мы все знаем приколы с конструкторами вектора, и их стремлению интерпретировать набор объектов внутри фигурных скобок как std::initializer_list. Я даже назвал функцию vector, чтобы вас надурить немного. Да и по правилам вывода типов auto, {1, 2, 3} тоже выведется в std::initializer_list. Но вывод в шаблонах - это другое. Компилятор не может правильно интерпретировать, что за сущность мы пытаемся засунуть в функцию, поэтому откажется это компилировать.
template <class T>
void func(std::shared_ptr<const T> & ptr) {}
func(std::shared_ptr<const int>{});
Вроде как должно быть int, но если внимательно присмотреться к сигнатуре функции, то можно рассмотреть, что она принимает неконстантную ссылку. А временный объект, который мы в нее передаем, не сможет привестить к неконстантной ссылке. Поэтому снова будет ошибка компиляции.
template <class T>
void func(T&& param) {}
const std::vector<int> vec;
func(vec);
Нет, здесь не будет ошибки компиляции. Слишком много уловок - нехорошо. Тут просто хороший кейс на проверку полноты усвоенного материала. Здесь param - универсальная ссылка. Передаем мы в функцию lvalue, а это значит, что тип Т точно будет ссылкой. Вопрос на что. Здесь никакой вложенности нет, поэтому ничего от типа
vec не убираем, а только добавляем сслочность. Ответ - const std::vector<int>&.Check your knowledge. Stay cool.
🔥24👍11❤2⚡1❤🔥1
Отпуск
Ребята, Грокаем С++ в отпуске! Посты будут выходить чуть реже, но все же будут и там запланированы несколько интересных тем.
Профессиональная карьера - это не только про непрерывную работу с 9 до 18, и вечер, занятый пилением пет-проектов. Имхо, но настоящий профессионал - человек, который знает, как грамотно отдыхать. И не имею ввиду просиживать штаны на работе.
Знаю кучу историй, когда люди не ходили в отпуск годами. Когда компания заставляла брать отпуск, а ребята брали его в выходные. Лишь бы ничего не пропустить. "А вдруг прод решит неожиданно заболеть?" Всегда надо быть на подхвате. "Да и какой отпуск вообще, С++ - это зизнь!!"
Что мы имеем в итоге? Человеку 30-40 лет, мир не видел, системные выгорания из-за переработок, перепады настроения, радикулит жопы из-за сидячей работы и дряблое тело из-за отсутствия активности. You name it.
Стратегия постоянного впахивания, конечно, имеет место быть и дает свои плоды. По-началу надо хреначить, чтобы выбиться из студенческой бедности и остальной серой массы, наконец устроиться на работу и начать приносить пользу.
Как только у вас есть приличное жилье и хорошая еда - давайте себе отдых. На самом деле все просто: загружены мозги? Не думай!
Сидеть в телефоне и смотреть видосики не катит. Мозг не отдыхает. Он продолжает анализировать большой объем информации. По итогу вы и не отдохнули, и ничего полезного не сделали.
На мой взгляд, есть 2 самых эффективных способа не думать: физическая работа и яркие впечатления.
80% всех наших клеток мозга - моторные нейроны, которые отвечают за движение и координацию. И только лишь небольшая часть отвечает за мыслительный процесс. Исследования четко показывают - занятие физическими нагрузками имеет долгосрочное позитивное влияние на когнитивные способности. Потому поход в зал - это ваши вложения не только в свое тело, но и в длительную и продуктивную карьеру.
Ну и яркие впечатления. Если вы посмотрите в ретроспективе на свою жизнь, то большинство ваших воспоминаний - какие-то яркие моменты из прошлого. Первое сальто с крыши гаража в снег, прыжок с парашютом, рождение ребенка - вот из чего состоит наша память. Эмоции так переливаются через край, что существуете только вы, этот момент и больше ничего. Ни тасок, ни дедлайнов, ни назойливых менеджеров. Такие события дают большой буст в жизненной энергии. Но не только. По итогу все рано или поздно понимают, что ради этих событий и стоит жить.
Отдыхайте, друзья. В будущем, скажете себе спасибо.
Be a professional. Stay cool.
#fun #commercial
Ребята, Грокаем С++ в отпуске! Посты будут выходить чуть реже, но все же будут и там запланированы несколько интересных тем.
Профессиональная карьера - это не только про непрерывную работу с 9 до 18, и вечер, занятый пилением пет-проектов. Имхо, но настоящий профессионал - человек, который знает, как грамотно отдыхать. И не имею ввиду просиживать штаны на работе.
Знаю кучу историй, когда люди не ходили в отпуск годами. Когда компания заставляла брать отпуск, а ребята брали его в выходные. Лишь бы ничего не пропустить. "А вдруг прод решит неожиданно заболеть?" Всегда надо быть на подхвате. "Да и какой отпуск вообще, С++ - это зизнь!!"
Что мы имеем в итоге? Человеку 30-40 лет, мир не видел, системные выгорания из-за переработок, перепады настроения, радикулит жопы из-за сидячей работы и дряблое тело из-за отсутствия активности. You name it.
Стратегия постоянного впахивания, конечно, имеет место быть и дает свои плоды. По-началу надо хреначить, чтобы выбиться из студенческой бедности и остальной серой массы, наконец устроиться на работу и начать приносить пользу.
Как только у вас есть приличное жилье и хорошая еда - давайте себе отдых. На самом деле все просто: загружены мозги? Не думай!
Сидеть в телефоне и смотреть видосики не катит. Мозг не отдыхает. Он продолжает анализировать большой объем информации. По итогу вы и не отдохнули, и ничего полезного не сделали.
На мой взгляд, есть 2 самых эффективных способа не думать: физическая работа и яркие впечатления.
80% всех наших клеток мозга - моторные нейроны, которые отвечают за движение и координацию. И только лишь небольшая часть отвечает за мыслительный процесс. Исследования четко показывают - занятие физическими нагрузками имеет долгосрочное позитивное влияние на когнитивные способности. Потому поход в зал - это ваши вложения не только в свое тело, но и в длительную и продуктивную карьеру.
Ну и яркие впечатления. Если вы посмотрите в ретроспективе на свою жизнь, то большинство ваших воспоминаний - какие-то яркие моменты из прошлого. Первое сальто с крыши гаража в снег, прыжок с парашютом, рождение ребенка - вот из чего состоит наша память. Эмоции так переливаются через край, что существуете только вы, этот момент и больше ничего. Ни тасок, ни дедлайнов, ни назойливых менеджеров. Такие события дают большой буст в жизненной энергии. Но не только. По итогу все рано или поздно понимают, что ради этих событий и стоит жить.
Отдыхайте, друзья. В будущем, скажете себе спасибо.
Be a professional. Stay cool.
#fun #commercial
🫡50🍾24❤🔥10🔥5⚡3🤓2
const rvalue reference
#опытным
В прошлом посте мельком упомянул эту конструкцию, а в этом решил раскрыть по-подробнее.
Правые ссылки были введены в С++11 и с тех пор помогают в реализации семантики перемещения. С помощью таких ссылок мы можем убрать ненужное глубокое копирование объектов и внедрить "перемещение" одного объекта в другой. Достигается это с помощью специальных методов: конструктора перемещения и перемещающего оператора присваивания. Выглядит это так:
Эти два специальных метода всегда имеют такую сигнатуру. Даже если их генерирует за нас компилятор.
То есть по идее, правые ссылки нужны, чтобы перенести из значение текущего объекта в новый объект. И для этого первоначальный объект нужно изменить.
Так за каким хреном нам тогда нужны константный правые ссылки? Чтобы что? С первого взгляда это выглядит так: мы принимаем правые все правые ссылки в перегрузку(неконстантные ссылки биндятся к константным), но все равно копируем объект, потому что ничего другого сделать не можем. Звучит, как бред.
Но все же есть применение у этой конструкции.
Дело в том, что T&& могут кастится к const T&, T&& и const T&&. Наиболее подходящей перегрузкой будет T&&, дальше const T&& и, наконец, const T&. А вот левые ссылки к правым вообще не могут преобразовываться.
Соотвественно, если мы хотим принимать только lvalue в функцию и никак не пропускать правые ссылки, то "Хьюстон, у нас проблема!". Если мы просто определим перегрузку для const T&, то rvalue reference все равно смогут попадать в эту перегрузку. Что нас сильно огорчает.
Однако мы можем совершить ход конем: пометить перегрузку const T&& удаленной. Так как удаленные функции все еще участвуют в разрешении перегрузок, то для T&& более подходящим выбором будет const T&&, нежели const T&. Но мы намеренно удаляем эту перегрузку и тогда компилятор выдает ошибку из-за того, что не может найти подходящий вариант функции.
Это применение и еще парочку других мы рассмотрим на реальных примерах в следующий раз.
Remove obstructing things from your life. Stay cool.
#cppcore #cpp11
#опытным
В прошлом посте мельком упомянул эту конструкцию, а в этом решил раскрыть по-подробнее.
Правые ссылки были введены в С++11 и с тех пор помогают в реализации семантики перемещения. С помощью таких ссылок мы можем убрать ненужное глубокое копирование объектов и внедрить "перемещение" одного объекта в другой. Достигается это с помощью специальных методов: конструктора перемещения и перемещающего оператора присваивания. Выглядит это так:
struct Movable {
Movable(int i) : num{new int(i)} {}
Movable(Movable&& other) {
num = other.num;
other.num = nullptr;
std::cout << "Don't have to copy in ctor\n";
}
Movable& operator=(Movable&& other) {
if (this != &other) {
delete num;
num = other.num;
other.num = nullptr;
std::cout << "Don't have to copy in assignment\n";
}
return *this;
}
~Movable() { delete num;}
int * num = nullptr;
}
Movable obj1{5};
Movable obj2{7};
Movable&& rvalue_ref = std::move(obj1);
Movable obj3{std::move(rvalue_ref)};
obj2 = std::move(obj3);
// OUTPUT
// Don't have to copy in ctor
// Don't have to copy in assignmentЭти два специальных метода всегда имеют такую сигнатуру. Даже если их генерирует за нас компилятор.
То есть по идее, правые ссылки нужны, чтобы перенести из значение текущего объекта в новый объект. И для этого первоначальный объект нужно изменить.
Так за каким хреном нам тогда нужны константный правые ссылки? Чтобы что? С первого взгляда это выглядит так: мы принимаем правые все правые ссылки в перегрузку(неконстантные ссылки биндятся к константным), но все равно копируем объект, потому что ничего другого сделать не можем. Звучит, как бред.
Но все же есть применение у этой конструкции.
Дело в том, что T&& могут кастится к const T&, T&& и const T&&. Наиболее подходящей перегрузкой будет T&&, дальше const T&& и, наконец, const T&. А вот левые ссылки к правым вообще не могут преобразовываться.
Соотвественно, если мы хотим принимать только lvalue в функцию и никак не пропускать правые ссылки, то "Хьюстон, у нас проблема!". Если мы просто определим перегрузку для const T&, то rvalue reference все равно смогут попадать в эту перегрузку. Что нас сильно огорчает.
Однако мы можем совершить ход конем: пометить перегрузку const T&& удаленной. Так как удаленные функции все еще участвуют в разрешении перегрузок, то для T&& более подходящим выбором будет const T&&, нежели const T&. Но мы намеренно удаляем эту перегрузку и тогда компилятор выдает ошибку из-за того, что не может найти подходящий вариант функции.
struct T{};
void f(T&) { std::cout << "lvalue ref\n"; }
void f(const T&) { std::cout << "const lvalue ref\n"; }
void f(const T&&) = delete; //{ std::cout << "const rvalue ref\n"; }
const T g() {
return T{};
}
int main() {
f(g()); // error: use of deleted function 'void f(const T&&)'
f(T{}); // error: use of deleted function 'void f(const T&&)'
}Это применение и еще парочку других мы рассмотрим на реальных примерах в следующий раз.
Remove obstructing things from your life. Stay cool.
#cppcore #cpp11
👍21🔥7❤6
Примеры использования const T&&
#опытным
В прошлый раз мы поговорили о том, что можно использовать константную правую ссылку для того, чтобы запретить принимать любые rvalue reference в функцию.
Для чего это может быть нужно?
Допустим, мы храним в поле класса в каком-то виде ссылку на объект. И нам бы очень не хотелось принимать в конструкторе rvalue reference, потому что возможно сразу же после выхода из конструктора для объектов вызовется деструктор и хана этим объектам. И встречаем UB из-за хранения битой ссылки.
Есть такой стандартный класс std::reference_wrapper и его функции помощники std::ref() и std::cref(). Поскольку std::reference_wrapper предполагает хранение ссылки только для lvalue, то стандарт удалил перегрузки std::ref() и std::cref(), которые принимают const T&&.
По той же самой причине такая перегрузка удалена у функции std::as_const, которая формирует левую ссылку на константный тип из аргумента.
Также константные правые ссылки используются в более сложных штуках, типа std::optional, когда нужно вернуть из него значение.
С этой же целью оно используется, например, и в std::get.
В таких случаях использование const T&& оправдано передачей информации и о ссылочности типа, и о его константности. Это важно в обобщенном программировании, потому что никто не знает с каким типом будет работать шаблонная сущность. Вы вполне можете получить константный временный объект std::optional(да и любого другого объекта), это синтаксически корректно. И чтобы геттер его внутреннего значение отражал свойства обертки, приходится перегружать эти геттеры для любых возможных параметров. Так вот например методы std::optional упомянутые выше вызовутся только для временных константных объектов. И эти свойства отображаются в возвращаемом значении.
Также не стоит забывать, что константность объекта не накладывает ультимативных ограничений на использование объекта. Есть мутабельные и статические поля, которые можно изменять, и плевать они хотели на вашу константность. А также указатели. Мы не можем менять сам указатель, но можем изменить объект, на который он указывает. Это немного расширяет спектр возможностей использования константных правых ссылок, но не прям существенно. В голову пришел очевидный пример - pimpl idiom. Согласно этой идиоме класс хранит указатель на реализацию, в которой может лежать все, что угодно. Все операции, которые как-то изменяют состояние объекта, влияют на данные внутри указателя. Поэтому снаружи кажется, что объект и не изменился. Да и старый объект можно будет использовать. Непонятно только, зачем менять привычные традиции использования правых ссылок, но все же.
Stay useful even if nobody understands you. Stay cool.
#template #cpp11 #STL
#опытным
В прошлый раз мы поговорили о том, что можно использовать константную правую ссылку для того, чтобы запретить принимать любые rvalue reference в функцию.
Для чего это может быть нужно?
Допустим, мы храним в поле класса в каком-то виде ссылку на объект. И нам бы очень не хотелось принимать в конструкторе rvalue reference, потому что возможно сразу же после выхода из конструктора для объектов вызовется деструктор и хана этим объектам. И встречаем UB из-за хранения битой ссылки.
Есть такой стандартный класс std::reference_wrapper и его функции помощники std::ref() и std::cref(). Поскольку std::reference_wrapper предполагает хранение ссылки только для lvalue, то стандарт удалил перегрузки std::ref() и std::cref(), которые принимают const T&&.
template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;
По той же самой причине такая перегрузка удалена у функции std::as_const, которая формирует левую ссылку на константный тип из аргумента.
template< class T >
constexpr std::add_const_t<T>& as_const( T& t ) noexcept;
Также константные правые ссылки используются в более сложных штуках, типа std::optional, когда нужно вернуть из него значение.
constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;
С этой же целью оно используется, например, и в std::get.
template< std::size_t I, class... Types >
constexpr const std::variant_alternative_t<I, std::variant<Types...>>&&
get( const std::variant<Types...>&& v );
template< class T, class... Types >
constexpr const T&& get( const std::variant<Types...>&& v );
В таких случаях использование const T&& оправдано передачей информации и о ссылочности типа, и о его константности. Это важно в обобщенном программировании, потому что никто не знает с каким типом будет работать шаблонная сущность. Вы вполне можете получить константный временный объект std::optional(да и любого другого объекта), это синтаксически корректно. И чтобы геттер его внутреннего значение отражал свойства обертки, приходится перегружать эти геттеры для любых возможных параметров. Так вот например методы std::optional упомянутые выше вызовутся только для временных константных объектов. И эти свойства отображаются в возвращаемом значении.
Также не стоит забывать, что константность объекта не накладывает ультимативных ограничений на использование объекта. Есть мутабельные и статические поля, которые можно изменять, и плевать они хотели на вашу константность. А также указатели. Мы не можем менять сам указатель, но можем изменить объект, на который он указывает. Это немного расширяет спектр возможностей использования константных правых ссылок, но не прям существенно. В голову пришел очевидный пример - pimpl idiom. Согласно этой идиоме класс хранит указатель на реализацию, в которой может лежать все, что угодно. Все операции, которые как-то изменяют состояние объекта, влияют на данные внутри указателя. Поэтому снаружи кажется, что объект и не изменился. Да и старый объект можно будет использовать. Непонятно только, зачем менять привычные традиции использования правых ссылок, но все же.
// MyClass.hpp
class MyClass {
public:
MyClass();
MyClass(int g_meat);
MyClass(const MyClass &&other); // const rvalue reference!
~MyClass();
int GetMeat() const;
private:
class Pimpl;
Pimpl *impl {};
};
// MyClass.cpp
class MyClass::Pimpl {
public:
int meat {42};
};
MyClass::MyClass() : impl {new Pimpl} { }
MyClass::MyClass(int g_meat) : MyClass() {
impl->meat = g_meat;
}
MyClass::MyClass(const MyClass &&other) : MyClass()
{
impl->meat = other.impl->meat;
other.impl->meat = 0;
}
MyClass::~MyClass() { delete impl; }
int MyClass::GetMeat() const {
return impl->meat;
}
// main.cpp
int main() {
const MyClass a {100500};
MyClass b (std::move(a)); // moving from const!
std::cout << a.GetMeat() << "\n"; // returns 0, b/c a is moved-from
std::cout << b.GetMeat() << "\n"; // returns 100500
}
Stay useful even if nobody understands you. Stay cool.
#template #cpp11 #STL
❤11👍8🔥5⚡3
Отдых
Мы уже поговорили о том, что важно отдыхать. Но это было в контексте отпуска и такого, основательного релакса. Но знаете, какое самое крутое состояние? Когда тебе не нужен отпуск. Тебе базово очень нравится твоя жизнь и ты не доводишь себя до такой усталости, что уже вынужден уходить на долгий перерыв, чтобы не сгореть дотла.
Но как этого достигнуть?
Грамотное распределение рабочего времени и перерывов - один из способов.
У вас есть 8 часов рабочего времени. Дада, дорогие программисты. Ни 10 и ни 12. Хардворк энивеа вычеркиваем из списка своих жизненных девизов. Надо работать не тяжело, а с умом. Нам голову создатели придумали не только для того, чтобы в нее есть. Работать долго может только четко выстроенная система.
И в эти 8 часов нужно впихнуть весь обьем насущных задач. Цель - амбициозная и нетривиальная. Но умные дяди придумали некоторые схемы, которые помогут несколько систематизировать этот хаос.
Для начала нужно составлять план на день. Желательно письменно, но можно и в голове, если вы Джимми Нейтроны. Это помогает видеть конкретные цели, при достижении которых мозг выделит дофамин, давая вам приятные ощущения и мотивацию двигаться дальше. Вам же приятно закрывать тикеты в жире? С собственным микропланированием вы можете закрывать несколько задач за день! Сколько приятных эмоций от зачеркивания пунков в своем плане, мммм.
Далее - приоритизация задач. Схем существует куча, но приведу ту, которая мне больше всего нравится.
Разделяем весь спектр задач на простые, средние и тяжелые. Как только у вас появилось это разбиение, вы делаете не более 1 тяжелой задачи + не более 3-х средних задач + не более 5 легких задач в день. Схема 1+3+5.
Разделение задач по сложности - вполне соответствует реальности среднего рабочего дня. Нужно ответить на почту, создать задачи, провести собес, сходить на митинг и тд. Все это разные по затрате энергии действия и очевидно, что это нужно учитывать при планировании своего дня.
Вот мы получили шаблон, куда мы можем вставлять свои реальные задачи. Теперь осталось только понять, сколько по времени выполнять таски разной сложности, чтобы появился адекватный мэтчинг.
Обычно цифры такие: тяжелая задача - 3-4 часа, средняя - 30-40 мин, легкая - 10 мин.
Счетоводы уже все посчитали и напряглись: даже при максимальных цифрах пропадает 1 час. Куда он тратится?
На отдых
Какое бы у вас не было суперское внимание, оно понижается при непрерывной и вовлеченной работе.
Делайте себе перерывы.
Для тех, кто работает из офиса - это вполне органичная вещь. Пошел, кофеек с коллегой попил, мозги переключились, отдохнули и готовы разрывать эти ваши си-плюс-плюсы.
Но для удаленщиков перерывы не естественны. И парадокс в том, что именно им больше всего они и нужны! Отсутствие естественных отвлекающих факторов приводит к тому, что человек садится за стол в 9 и до вечера прожевывает пятой точкой дырку в этом стуле, зачастую без обеда. Кстати, ставь лайк, если пролеживаешь дырку в диване и зарабатываешь остеохондроз aka работаешь лежа(посмотрим сколько нас).
Завершил какой-то логический блок - отдохнул. Надолго застрял - отдохнул. Даже по гостам нужно делать перерывы. Да, может не так много суммарно. Но мы ж программисты. Мы тонкие и творческие личности🦋🌸. Нам можно.
Опять же. Этот пост - не руководство к действию, которое нужно выполнить в строгости. Мы, как и бизнес, должны быть agile и подстраивать свои концепции под ситуацию. Иногда надо и поработать побольше. Иногда, когда чувствуешь, что не вывозишь, отдохнуть побольше.
В общем, вроде простые рекомендации, но внедряя их вы ощущите всю мощь успешного успеха и расти будете не до синьора/лида/Илона Маска, а до самих просторов космоса.
Stay smarter. Stay cool.
#fun #commertial
Мы уже поговорили о том, что важно отдыхать. Но это было в контексте отпуска и такого, основательного релакса. Но знаете, какое самое крутое состояние? Когда тебе не нужен отпуск. Тебе базово очень нравится твоя жизнь и ты не доводишь себя до такой усталости, что уже вынужден уходить на долгий перерыв, чтобы не сгореть дотла.
Но как этого достигнуть?
Грамотное распределение рабочего времени и перерывов - один из способов.
У вас есть 8 часов рабочего времени. Дада, дорогие программисты. Ни 10 и ни 12. Хардворк энивеа вычеркиваем из списка своих жизненных девизов. Надо работать не тяжело, а с умом. Нам голову создатели придумали не только для того, чтобы в нее есть. Работать долго может только четко выстроенная система.
И в эти 8 часов нужно впихнуть весь обьем насущных задач. Цель - амбициозная и нетривиальная. Но умные дяди придумали некоторые схемы, которые помогут несколько систематизировать этот хаос.
Для начала нужно составлять план на день. Желательно письменно, но можно и в голове, если вы Джимми Нейтроны. Это помогает видеть конкретные цели, при достижении которых мозг выделит дофамин, давая вам приятные ощущения и мотивацию двигаться дальше. Вам же приятно закрывать тикеты в жире? С собственным микропланированием вы можете закрывать несколько задач за день! Сколько приятных эмоций от зачеркивания пунков в своем плане, мммм.
Далее - приоритизация задач. Схем существует куча, но приведу ту, которая мне больше всего нравится.
Разделяем весь спектр задач на простые, средние и тяжелые. Как только у вас появилось это разбиение, вы делаете не более 1 тяжелой задачи + не более 3-х средних задач + не более 5 легких задач в день. Схема 1+3+5.
Разделение задач по сложности - вполне соответствует реальности среднего рабочего дня. Нужно ответить на почту, создать задачи, провести собес, сходить на митинг и тд. Все это разные по затрате энергии действия и очевидно, что это нужно учитывать при планировании своего дня.
Вот мы получили шаблон, куда мы можем вставлять свои реальные задачи. Теперь осталось только понять, сколько по времени выполнять таски разной сложности, чтобы появился адекватный мэтчинг.
Обычно цифры такие: тяжелая задача - 3-4 часа, средняя - 30-40 мин, легкая - 10 мин.
Счетоводы уже все посчитали и напряглись: даже при максимальных цифрах пропадает 1 час. Куда он тратится?
На отдых
Какое бы у вас не было суперское внимание, оно понижается при непрерывной и вовлеченной работе.
Делайте себе перерывы.
Для тех, кто работает из офиса - это вполне органичная вещь. Пошел, кофеек с коллегой попил, мозги переключились, отдохнули и готовы разрывать эти ваши си-плюс-плюсы.
Но для удаленщиков перерывы не естественны. И парадокс в том, что именно им больше всего они и нужны! Отсутствие естественных отвлекающих факторов приводит к тому, что человек садится за стол в 9 и до вечера прожевывает пятой точкой дырку в этом стуле, зачастую без обеда. Кстати, ставь лайк, если пролеживаешь дырку в диване и зарабатываешь остеохондроз aka работаешь лежа(посмотрим сколько нас).
Завершил какой-то логический блок - отдохнул. Надолго застрял - отдохнул. Даже по гостам нужно делать перерывы. Да, может не так много суммарно. Но мы ж программисты. Мы тонкие и творческие личности🦋🌸. Нам можно.
Опять же. Этот пост - не руководство к действию, которое нужно выполнить в строгости. Мы, как и бизнес, должны быть agile и подстраивать свои концепции под ситуацию. Иногда надо и поработать побольше. Иногда, когда чувствуешь, что не вывозишь, отдохнуть побольше.
В общем, вроде простые рекомендации, но внедряя их вы ощущите всю мощь успешного успеха и расти будете не до синьора/лида/Илона Маска, а до самих просторов космоса.
Stay smarter. Stay cool.
#fun #commertial
❤33👍9🫡7🔥3❤🔥2😁2
ParamType - не ссылка и не указатель
#новичкам
Список постов по теме
В при такой форме параметра шаблонной функции, который в данном случае не является ни ссылкой, ни указателем, мы имеем дело с передачей аргумента по значению. Все как с нешаблонными функциями.
Это значит, что param - совершенно новый объект типа Т, который создается из аргумента, переданного функции. После создания он живет своей жизнь и никак не зависит от исходного объекта.
Есть кстати распространенное заблуждение или недоговаривание, что при передаче объекта в функцию по значению происходит копирование. Это не совсем правда. Вызывается конструктор объекта на основе переданного параметра. А вот какой именно конструктор вызовется - copy или move - определяется типом ссылочности аргумента. Передадут lvalue - вызовется copy ctor, передадут rvalue reference - вызовется move ctor. Короткий пример:
Как видим, при передаче аргумента через std::move происходит вызов мув конструктора.
Кстати, недавно дошел до очень простого объяснения мув-семантики и всего, что вокруг нее вертиться. Буквально за один пост все поймут всё про нее. Если хотите такой пост - жмакайте кита)
Вернемся к шаблонам
Как в этой ситуации выводится шаблонный тип?
Если у типа expression есть верхняя ссылочность/константность/волатильность - все в мусорку, оставшееся - тип Т.
Обратите внимание: хотя
С указателем похожая история: он как бы копируется в функцию. В функции уже другой указатель - param - и он не обязан быть сам по себе константным. И согласно правилам вывода типов, таковым и не является.
Если шаблонный параметр немного сложнее - например, тип Т вложен в другой шаблонный тип, то возвращаемся к нашей капусте. Снимаем столько слоев с типа expression, сколько явно определено для типа param - и это будет тип Т. Аргумент передается также по значению.
На этом мы заканчиваем рассматривать мажорные кейсы видов шаблонных параметров функций. Дальше будем какие-то менее встречающиеся примеры рассматривать.
Be independent subject. Stay cool.
#cppcore #template
#новичкам
Список постов по теме
template <class T>
void func(T param) {...}
func(expression);
В при такой форме параметра шаблонной функции, который в данном случае не является ни ссылкой, ни указателем, мы имеем дело с передачей аргумента по значению. Все как с нешаблонными функциями.
Это значит, что param - совершенно новый объект типа Т, который создается из аргумента, переданного функции. После создания он живет своей жизнь и никак не зависит от исходного объекта.
Есть кстати распространенное заблуждение или недоговаривание, что при передаче объекта в функцию по значению происходит копирование. Это не совсем правда. Вызывается конструктор объекта на основе переданного параметра. А вот какой именно конструктор вызовется - copy или move - определяется типом ссылочности аргумента. Передадут lvalue - вызовется copy ctor, передадут rvalue reference - вызовется move ctor. Короткий пример:
struct Test {
Test() = default;
Test(const Test& other) {
std::cout << "Copy ctor" << std::endl;
}
Test(Test&& other) {
std::cout << "Move ctor" << std::endl;
}
};
template<class T>
void fun(T t) {
std::cout << "Hello, subscribers!!!" << std::endl;
}
int main () {
Test t;
fun(t);
fun(std::move(t))
}Copy ctor
Hello, subscribers!!!
Move ctor
Hello, subscribers!!!
Как видим, при передаче аргумента через std::move происходит вызов мув конструктора.
Кстати, недавно дошел до очень простого объяснения мув-семантики и всего, что вокруг нее вертиться. Буквально за один пост все поймут всё про нее. Если хотите такой пост - жмакайте кита)
Вернемся к шаблонам
Как в этой ситуации выводится шаблонный тип?
Если у типа expression есть верхняя ссылочность/константность/волатильность - все в мусорку, оставшееся - тип Т.
int x = 42;
const int cx = x;
const int& rx = x;
const int * const px = &x;
func(x); // T's and param's types are both int
func(cx); // T's and param's types are again both int
func(rx); // T's and param's types are still both int
func(px); // T's and param's types are const int*
Обратите внимание: хотя
cx и rx представляют константные значения, param не является константой. В этом есть смысл. param — это объект, полностью независимый от cx и rx, копия cx или rx. Тот факт, что cx и rx не могут быть изменены, ничего не говорит о том, можно ли изменять param. Вот почему константность expression игнорируется при определении типа параметра: то, что expression не может быть изменено, не означает, что его копия не может быть изменена.С указателем похожая история: он как бы копируется в функцию. В функции уже другой указатель - param - и он не обязан быть сам по себе константным. И согласно правилам вывода типов, таковым и не является.
Если шаблонный параметр немного сложнее - например, тип Т вложен в другой шаблонный тип, то возвращаемся к нашей капусте. Снимаем столько слоев с типа expression, сколько явно определено для типа param - и это будет тип Т. Аргумент передается также по значению.
template <class T>
void func(std::vector<T> param) {...}
std::vector<int> vec;
const std::vector<int> const_vec;
func(vec); // T is int, param's type is std::vector<int>
func(const_vec); // T is int, param's type is std::vector<int>
На этом мы заканчиваем рассматривать мажорные кейсы видов шаблонных параметров функций. Дальше будем какие-то менее встречающиеся примеры рассматривать.
Be independent subject. Stay cool.
#cppcore #template
🐳83👍9🔥3❤2
Правильный swap двух объектов Ч1
В статье про swap идиому мы реализовали свап объектов класса через дружественную функцию. Сегодня поговорим почему так делать правильно.
В принципе нам нужна функциональность, которая сможет обменять данных двух объектов местами. Она может называться как угодно, никто не запрещает назвать вам swap функцию как TheMostTrickyFunction.
Но так делать не очень удобно. Все привыкли использовать std::swap для обмена значений. Поэтому логично как минимум назвать функцию swap.
Дальше будем рассматривать по очереди возможные варианты.
Можно определить метод swap внутри класса:
И хоть это будет работать в пользовательском коде just fine, но мы не сможем для такого типа например использовать std::sort, которая вызывает свободную функцию swap.
Но мы можем найти примеры такого дизайна даже в стандартной библиотеке. Например std::vector имеем метод swap, который обменивает данные двух векторов. Но тут важен контекст: до появления мув-семантики обмен векторов через std::swap приводило бы к нежелательным копированиям. Поэтому в те времена многие объекты имели свою оптимизированную версию в виде метода.
Сейчас вы можете использовать std::swap на двух векторах и не парится по поводу перфоманса. Так что просто swap метод класса нам не подходит.
Раз мы хотим использовать std::swap, то может тогда просто специализируем эту функцию для нашего типа в скоупе std? Давайте попробуем.
И это даже может и заработает. Но С++20 говорит нам, что специализировать шаблонные функции из неймспейса std - неопределенное поведение. Поэтому этот вариант - совсем не вариант.
Попробуем определение свободной функции swap в неймспейсе класса
Однако это выглядит просто как обертка для метода swap, который больше нигде не используется. Может как-то схлопнуть две эти сущности?
Сделаем эту свободную функцию дружественной нашему классу! Тогда можно выкинуть ненужный метод и оставить просто функцию.
Так-то лучше. Все работает и выглядит культурно. Далее поговорим про то, что должно быть внутри функции.
Be nice. Stay cool.
#template #cppcore #cpp20
В статье про swap идиому мы реализовали свап объектов класса через дружественную функцию. Сегодня поговорим почему так делать правильно.
В принципе нам нужна функциональность, которая сможет обменять данных двух объектов местами. Она может называться как угодно, никто не запрещает назвать вам swap функцию как TheMostTrickyFunction.
Но так делать не очень удобно. Все привыкли использовать std::swap для обмена значений. Поэтому логично как минимум назвать функцию swap.
Дальше будем рассматривать по очереди возможные варианты.
Можно определить метод swap внутри класса:
struct my_type
{
void swap(my_type&) { /* swap members / }
};
И хоть это будет работать в пользовательском коде just fine, но мы не сможем для такого типа например использовать std::sort, которая вызывает свободную функцию swap.
Но мы можем найти примеры такого дизайна даже в стандартной библиотеке. Например std::vector имеем метод swap, который обменивает данные двух векторов. Но тут важен контекст: до появления мув-семантики обмен векторов через std::swap приводило бы к нежелательным копированиям. Поэтому в те времена многие объекты имели свою оптимизированную версию в виде метода.
Сейчас вы можете использовать std::swap на двух векторах и не парится по поводу перфоманса. Так что просто swap метод класса нам не подходит.
Раз мы хотим использовать std::swap, то может тогда просто специализируем эту функцию для нашего типа в скоупе std? Давайте попробуем.
namespace std
{
template <>
void swap(my_type& one, my_type& two)
{
one.swap(two);
}
}
И это даже может и заработает. Но С++20 говорит нам, что специализировать шаблонные функции из неймспейса std - неопределенное поведение. Поэтому этот вариант - совсем не вариант.
Попробуем определение свободной функции swap в неймспейсе класса
namespace my_ns {
struct my_type
{
void swap(my_type&) { / swap members */ }
};
void swap( my_type<T> & lhs, my_type<T> & rhs ) noexcept
{
lhs.swap(rhs);
}
}Однако это выглядит просто как обертка для метода swap, который больше нигде не используется. Может как-то схлопнуть две эти сущности?
Сделаем эту свободную функцию дружественной нашему классу! Тогда можно выкинуть ненужный метод и оставить просто функцию.
struct my_type
{
friend void swap(my_type& first, my_type& second) noexcept {
// swap
}
};
Так-то лучше. Все работает и выглядит культурно. Далее поговорим про то, что должно быть внутри функции.
Be nice. Stay cool.
#template #cppcore #cpp20
❤🔥15👍7⚡2❤2
Правильный swap двух объектов Ч2
В прошлый раз мы разобрались, почему swap лучше всего определять через дружественную функцию. Сегодня разберем то, что должно быть у нее внутри.
Очень хочется написать просто типа такого:
И это работает для тривиальных типов Type1, Type2. Если у вас тривиальные члены - пожалуйста пользуйтесь.
Но если Type1, Type2 - пользовательские типы не из стандартной библиотеки? Понятное дело, что для стдшных типов есть специализации std::swap. Но вот для обычных смертных - нет. И если для этого смертного типа определена своя функция swap, которая лучше знает, как обменивать данные, то таким образом вы никогда ее не вызовете. А если какой-то из ваших мемберов не удовлетворяет условию std::is_move_constructible_v<T> && std::is_move_assignable_v<T>, то вы вообще не скомпилируете этот код.
Выход - нужно как-то разрешить искать функцию swap в неймспейсе класса. Так как она более специализирована под ваш конкретный тип, то она будет предпочтительнее, чем стдшная, и будет вызываться вместо него.
Такая штука есть и называется она ADL или Argument Dependent Lookup. С ее помощью мы можем найти нужную функцию по типу аргумента. Можно написать:
И если для Type1 и Type2 определены свои функции swap, то они найдутся и все скомпилируется.
Однако выискивать для каждого типа своего мембера, есть ли у него своя собственная swap - задача неблагодарная. Хотелось бы и рыбку съесть имашку за ляшку не задумываться о выборе нужной функции и положить эту задачу на плечи компилятора. И это можно сделать!
Какая тут магия произошла. Мы попрежнему используем ADL для поиска наиболее подходящей функции. Но если мы ее не нашли, то остается бэкап в виде std::swap, которая может вызваться благодаря using std::swap.
Примерно так и выглядит "каноничный" вид функции swap для ваших кастомных классов.
Sit on both chairs. Stay cool.
#cppcore
В прошлый раз мы разобрались, почему swap лучше всего определять через дружественную функцию. Сегодня разберем то, что должно быть у нее внутри.
Очень хочется написать просто типа такого:
struct my_type
{
friend void swap(my_type& first, my_type& second) noexcept {
std::swap(first.iss.onember_1, second.member_1);
std::swap(first.iss.onember_2, second.member_2);
}
Type1 member1;
Type2 member2;
};
И это работает для тривиальных типов Type1, Type2. Если у вас тривиальные члены - пожалуйста пользуйтесь.
Но если Type1, Type2 - пользовательские типы не из стандартной библиотеки? Понятное дело, что для стдшных типов есть специализации std::swap. Но вот для обычных смертных - нет. И если для этого смертного типа определена своя функция swap, которая лучше знает, как обменивать данные, то таким образом вы никогда ее не вызовете. А если какой-то из ваших мемберов не удовлетворяет условию std::is_move_constructible_v<T> && std::is_move_assignable_v<T>, то вы вообще не скомпилируете этот код.
Выход - нужно как-то разрешить искать функцию swap в неймспейсе класса. Так как она более специализирована под ваш конкретный тип, то она будет предпочтительнее, чем стдшная, и будет вызываться вместо него.
Такая штука есть и называется она ADL или Argument Dependent Lookup. С ее помощью мы можем найти нужную функцию по типу аргумента. Можно написать:
struct my_type
{
friend void swap(my_type& first, my_type& second) noexcept {
swap(first.iss.onember_1, second.member_1);
swap(first.iss.onember_2, second.member_2);
}
Type1 member1;
Type2 member2;
};
И если для Type1 и Type2 определены свои функции swap, то они найдутся и все скомпилируется.
Однако выискивать для каждого типа своего мембера, есть ли у него своя собственная swap - задача неблагодарная. Хотелось бы и рыбку съесть и
struct my_type
{
friend void swap(my_type& first, my_type& second) noexcept {
using std::swap;
swap(first.iss.onember_1, second.member_1);
swap(first.iss.onember_2, second.member_2);
}
Type1 member1;
Type2 member2;
};
Какая тут магия произошла. Мы попрежнему используем ADL для поиска наиболее подходящей функции. Но если мы ее не нашли, то остается бэкап в виде std::swap, которая может вызваться благодаря using std::swap.
Примерно так и выглядит "каноничный" вид функции swap для ваших кастомных классов.
Sit on both chairs. Stay cool.
#cppcore
🔥22👍15❤4🐳1
Ревью
Продолжаем разбирать примеры существующего кода и совместно выявлять в них проблемы. Сегодня будет не самый продовый пример, но интересный по многим моментам. В основном, из-за многопоточки.
Призываю всех участвовать в обсуждении и писать свои варианты того, что в этом коде не так и как это можно улучшить. Начинающие может пока совсем не поймут, что здесь происходит. Но вот продолжающим будет очень полезно почитать ваши рассуждения и узнать много нового.
Не обязательно писать большие тирады или искать заковыристые проблемы. Если вы нашли ошибку - поделитесь ей в комментах. Это отличная практика код #ревью.
Кусок кода прикреплю картинкой к посту. Объединенный разбор опубликую завтра. Как всегда, самые продуктивные ревьюеры будут упомянуты в этом разборе.
Analyse your life. Stay cool.
Продолжаем разбирать примеры существующего кода и совместно выявлять в них проблемы. Сегодня будет не самый продовый пример, но интересный по многим моментам. В основном, из-за многопоточки.
Призываю всех участвовать в обсуждении и писать свои варианты того, что в этом коде не так и как это можно улучшить. Начинающие может пока совсем не поймут, что здесь происходит. Но вот продолжающим будет очень полезно почитать ваши рассуждения и узнать много нового.
Не обязательно писать большие тирады или искать заковыристые проблемы. Если вы нашли ошибку - поделитесь ей в комментах. Это отличная практика код #ревью.
Кусок кода прикреплю картинкой к посту. Объединенный разбор опубликую завтра. Как всегда, самые продуктивные ревьюеры будут упомянуты в этом разборе.
Analyse your life. Stay cool.
1👍10🔥5❤🔥4
Результаты ревью
Всем большое спасибо за активность, было интересно почитать ваши замечания/вопросы/споры/предложения. Выявились 2 явных лидера по объему найденных недостатков. Так как рассуждения у них были объемные и не строго структурированные, то упомяну обоих. Давайте похлопаем Дмитрию и Михаилу👏👏👏👏
Теперь саммари по пунктам:
❗️ Используется странный стиль глобальные переменные + функции. Много чего можно объединить и/или завернуть в классы.
❗️ Если уж используете глобальные переменные в цпп файле(а это скорее всего он и есть), то используйте пометку static, чтобы не засорять именами скоуп. К функциям это тоже относится.
❗️ Обильное использование сырых указателей на объекты класса. В данном случае это точно code smell и все нужно переделать на умные указатели.
❗️ Бессмысленно использование более сложного гарда std::unique_lock. Обычно он используется в тандеме с кондварами. Здесь будет достаточно std::lock_guard и выделение отдельного скоупа критической секции с помощью фигурных скобок {}.
❗️ Странное завершение цикла при достижении конца очереди. Это накладывает на вызывающий код ответственность за обработку этой ситуации. Конкретно в таком простом варианте очереди на мьютексте логично оставить цикл бесконечным и при достижении конца продолжать цикл заново.
❗️ Странный слип под локом в DequeueJob. Слишком жирно потоку преднамеренно спать с загробастанным мьютексом. Наверное автор просто не подумал, но все же так делать не надо и этот слип нужно вынести из критической секции.
❗️ Странный принт под локом EnqueueJob. Слишком долгая операции и семантически не относящаяся к критической секции. Нужно вынести за пределы лока.
❗️ Странный выбор контейнера для очереди. Если уж так не хочется писать свой класс, то в STL есть прекрасный std::queue, который как бы прям прыгает с экрана монитора и говорит: "Нужна очередь и выбираешь не меня? Больной ублюдок..."
❗️ Так как NewJob - довольно маленькая структура, то передавать ее везде можно по значению, вместо указателей. Ну или сразу использовать std::unique_ptr с заделом на будущее ообогащение задачи.
❗️ Нет graceful завершения обработки очереди. Нужно как-то обрабатывать ситуацию, когда нам захочется закончить разгребать очередь и возможно сделать еще какие-то действия, но мы не хотим это делать через простой head shot приложения.
❗️ Стандартный подход к написанию очередей - использование кондваров. Консюмер спит до тех пор, пока продюсер не уведомит его о наличии новой задачи. Пример можно посмотреть тут.
❗️ Архитектурные решения в этом коде довольно бедные. Понятно, что код простой и не претендует на высокие материи и на место на обложке Cosmopolitan. Но хоть что-то же должно быть. По дизайну видение у всех может быть разное, но тут явно все хромает.
❗️ Уж не буду отдельными пунктами упоминать мелкие странные вещи типа "delete job, job = Null" или конструктор для простой структуры(можно агрегатную инициализацию использовать), юзинг и тд.
Итого довольно внушительный список получился. Конечно, это не продовый код и писал его человек явно в начале своего пути знакомства с многопоточкой и плюсами в целом. Так что не судите строго этого маленького китайского мальчика. Никто не родился вундеркиндом по С++.
"Исправленную версию" можете найти в комментах.
Fix your flaws. Stay cool.
Всем большое спасибо за активность, было интересно почитать ваши замечания/вопросы/споры/предложения. Выявились 2 явных лидера по объему найденных недостатков. Так как рассуждения у них были объемные и не строго структурированные, то упомяну обоих. Давайте похлопаем Дмитрию и Михаилу👏👏👏👏
Теперь саммари по пунктам:
❗️ Используется странный стиль глобальные переменные + функции. Много чего можно объединить и/или завернуть в классы.
❗️ Если уж используете глобальные переменные в цпп файле(а это скорее всего он и есть), то используйте пометку static, чтобы не засорять именами скоуп. К функциям это тоже относится.
❗️ Обильное использование сырых указателей на объекты класса. В данном случае это точно code smell и все нужно переделать на умные указатели.
❗️ Бессмысленно использование более сложного гарда std::unique_lock. Обычно он используется в тандеме с кондварами. Здесь будет достаточно std::lock_guard и выделение отдельного скоупа критической секции с помощью фигурных скобок {}.
❗️ Странное завершение цикла при достижении конца очереди. Это накладывает на вызывающий код ответственность за обработку этой ситуации. Конкретно в таком простом варианте очереди на мьютексте логично оставить цикл бесконечным и при достижении конца продолжать цикл заново.
❗️ Странный слип под локом в DequeueJob. Слишком жирно потоку преднамеренно спать с загробастанным мьютексом. Наверное автор просто не подумал, но все же так делать не надо и этот слип нужно вынести из критической секции.
❗️ Странный принт под локом EnqueueJob. Слишком долгая операции и семантически не относящаяся к критической секции. Нужно вынести за пределы лока.
❗️ Странный выбор контейнера для очереди. Если уж так не хочется писать свой класс, то в STL есть прекрасный std::queue, который как бы прям прыгает с экрана монитора и говорит: "Нужна очередь и выбираешь не меня? Больной ублюдок..."
❗️ Так как NewJob - довольно маленькая структура, то передавать ее везде можно по значению, вместо указателей. Ну или сразу использовать std::unique_ptr с заделом на будущее ообогащение задачи.
❗️ Нет graceful завершения обработки очереди. Нужно как-то обрабатывать ситуацию, когда нам захочется закончить разгребать очередь и возможно сделать еще какие-то действия, но мы не хотим это делать через простой head shot приложения.
❗️ Стандартный подход к написанию очередей - использование кондваров. Консюмер спит до тех пор, пока продюсер не уведомит его о наличии новой задачи. Пример можно посмотреть тут.
❗️ Архитектурные решения в этом коде довольно бедные. Понятно, что код простой и не претендует на высокие материи и на место на обложке Cosmopolitan. Но хоть что-то же должно быть. По дизайну видение у всех может быть разное, но тут явно все хромает.
❗️ Уж не буду отдельными пунктами упоминать мелкие странные вещи типа "delete job, job = Null" или конструктор для простой структуры(можно агрегатную инициализацию использовать), юзинг и тд.
Итого довольно внушительный список получился. Конечно, это не продовый код и писал его человек явно в начале своего пути знакомства с многопоточкой и плюсами в целом. Так что не судите строго этого маленького китайского мальчика. Никто не родился вундеркиндом по С++.
"Исправленную версию" можете найти в комментах.
Fix your flaws. Stay cool.
👍21👏9❤5❤🔥2
Теперь всякий раз, когда какой-нибудь зеленый неокрепший птенец, мечтающий стать гордым плюсовым орлом, будет вас спрашивать "Что такое указатель?" - вы знаете, что ему показать.
1🔥99🤣61😁8❤4👍4👏2
std::swap и ADL
В коммьюнити есть определенное заблуждение, что когда мы вызываем std::swap для наших кастомных типов, эта функция в начале ищет через ADL самую подходящую перегрузку, которую мы возможно определили, и только в случае неудачи вызывается дефолтная реализация обмена. Это конечно не так и вот причины.
Первая, довольно косвенная. Все алгоритмы стандартной библиотеки никогда не используют вызов std::swap напрямую для обмена элементов последовательности. Там все делается как в предыдущем посте с помощью using std::swap, чтобы как раз разрешить ADL найти самую подходящую перегрузку. Зачем это делать, если std::swap и так самостоятельно ищет через ADL?
Вторая - подобная реализация std::swap вгоняет в бесконечную рекурсию следующий код:
Неважно хороший ли это код или нет, он может встречаться в проектах. Зачем определять свою функцию swap через std::swap - загадка, но это работает.
Почему вообще должно быть важно, что работает такой плохой код? Потому что обратная совместимость. Такой код вполне мог существовать и использование ADL внутри std::swap его бы сломало.
Тем не менее были предложения в стандарт, которые предлагали разрешить ADL внутри std::swap. И они вроде даже попали в драфт С++20, но в сам стандарт так и не вошли. Думаю, что в том числе и по причине обратной совместимости.
Так что для правильного свопа необходимо приписывать using std::swap. Или нет?
Я уже говорил, что в стандартной библиотеке алгоритмов не используется обмен элементов последовательности через std::swap. Но не сказал, как именно. А обменивают их через std::iter_swap(логично, там же итераторы используются). И вот его возможная имплементация:
То, что нужно! Теперь можно писать так:
Немного корявенько, зато думать не надо.
Ну или можно бустовскую версию свапа взять, которая делает примерно то же, что и iter_swap. Но надо ли вам тянуть буст - это вопрос.
Properly exchange values. Stay cool.
#cppcore
В коммьюнити есть определенное заблуждение, что когда мы вызываем std::swap для наших кастомных типов, эта функция в начале ищет через ADL самую подходящую перегрузку, которую мы возможно определили, и только в случае неудачи вызывается дефолтная реализация обмена. Это конечно не так и вот причины.
Первая, довольно косвенная. Все алгоритмы стандартной библиотеки никогда не используют вызов std::swap напрямую для обмена элементов последовательности. Там все делается как в предыдущем посте с помощью using std::swap, чтобы как раз разрешить ADL найти самую подходящую перегрузку. Зачем это делать, если std::swap и так самостоятельно ищет через ADL?
Вторая - подобная реализация std::swap вгоняет в бесконечную рекурсию следующий код:
namespace ns
{
struct foo
{
foo() : i(0) {}
int i;
};
void swap(foo& lhs, foo& rhs)
{
std::swap(lhs, rhs);
}
}
template <typename T>
void do_swap(T& lhs, T& rhs)
{
std::swap(lhs, rhs);
}
int main()
{
ns::foo a, b;
do_swap(a, b);
}
Неважно хороший ли это код или нет, он может встречаться в проектах. Зачем определять свою функцию swap через std::swap - загадка, но это работает.
Почему вообще должно быть важно, что работает такой плохой код? Потому что обратная совместимость. Такой код вполне мог существовать и использование ADL внутри std::swap его бы сломало.
Тем не менее были предложения в стандарт, которые предлагали разрешить ADL внутри std::swap. И они вроде даже попали в драфт С++20, но в сам стандарт так и не вошли. Думаю, что в том числе и по причине обратной совместимости.
Так что для правильного свопа необходимо приписывать using std::swap. Или нет?
Я уже говорил, что в стандартной библиотеке алгоритмов не используется обмен элементов последовательности через std::swap. Но не сказал, как именно. А обменивают их через std::iter_swap(логично, там же итераторы используются). И вот его возможная имплементация:
template<class ForwardIt1, class ForwardIt2>
constexpr //< since C++20
void iter_swap(ForwardIt1 a, ForwardIt2 b)
{
using std::swap;
swap(*a, *b);
}
То, что нужно! Теперь можно писать так:
struct my_type
{
friend void swap(my_type& first, my_type& second) noexcept {
std::iter_swap(&first.iss.onember_1, &second.member_1);
std::iter_swap(&first.iss.onember_2, &second.member_2);
}
Type1 member1;
Type2 member2;
};
Немного корявенько, зато думать не надо.
Ну или можно бустовскую версию свапа взять, которая делает примерно то же, что и iter_swap. Но надо ли вам тянуть буст - это вопрос.
Properly exchange values. Stay cool.
#cppcore
🔥14👍5❤2
Зачем вообще нужен кастомный swap?
Коротко - незачем)
Но как всегда есть нюансы. Забайтились? Погнали разбираться.
Как всю историю человечества разделяет Рождество Христово, так и история С++ делится на две эпохи появлением стандарта С++11. Получается, что С++11 - Иисус в мире плюсов...
И вот до С++11 мы не имели семантики перемещения и функция std::swap обменивала два значения через копирование. Ну и естественно это никому не нравилось. Зачем такой оверхед, когда мне нужно только местами данные поменять?
И вот в те времена кастомная функция swap была как нельзя кстати. Именно поэтому std::vector имеет отдельный метод swap. Рудимент архаичного прошлого...
С тех пор все стандартные алгоритмы в первую очередь ищут use-defined swap и уже на крайняк используют std::swap.
Если ваш класс управлял хоть каким-то ресурсом, даже строкой, вам нужен был свап.
Но по сути-то, свап - это такой одновременный мув друг в друга(идейно). Ну и с появлением мув-семантики стандратная swap стала выглядеть именно так, как нам нужно идейно:
Эта версия свапа делает ровно то, что ожидали практически от всех кастомных swap'ов - эффективный обмен двух значений.
Она позволяет даже некопируемым объектам, типа стримов, мьютексов и прочих, обменяться местами. То есть она буквально отобрала весь хлеб у кастомной swap: теперь стандратная функция делает такой же эффективный обмен значениями, плюс может также обменять некопируемые объекты. Красота!
Но у самописной swap остается одно преимущество: Не происходит никаких вызовов конструкторов классов обмениваемых объектов. Мы напрямую обмениваем содержимое объектов. А std::swap все-таки вызывает один мув-конструктор и 2 мув присваивания. О производительности надо думать...
А еще надо думать об оптимизациях компилятора. Специальные методы могут быть заинлайнены и std::swap превратится ровно в то же, что сгенерирует компилятор для вашей самописной обменивалки.
Также некоторый легаси код может использовать в своих кишках именно метод swap, поэтому чтобы пользоваться этим кодом, нужно реализовывать метод. Но это не то, что бы частая история.
Итог какой: кастомный своп был придуман, в основном, чтобы эффективно обменивать объекты. std::swap на стероидах мув-семантики позволяет делать это очень эффективно. Самописный своп имеет на первый взгляд незначительные преимущества по производительности. Но на практике как всегда надо тестировать оба варианта. Ну или не заниматься преждевременной оптимизацией и использоват std::swap.
Use standard things. Stay cool.
#cppcore #cpp11
Коротко - незачем)
Но как всегда есть нюансы. Забайтились? Погнали разбираться.
Как всю историю человечества разделяет Рождество Христово, так и история С++ делится на две эпохи появлением стандарта С++11. Получается, что С++11 - Иисус в мире плюсов...
И вот до С++11 мы не имели семантики перемещения и функция std::swap обменивала два значения через копирование. Ну и естественно это никому не нравилось. Зачем такой оверхед, когда мне нужно только местами данные поменять?
И вот в те времена кастомная функция swap была как нельзя кстати. Именно поэтому std::vector имеет отдельный метод swap. Рудимент архаичного прошлого...
С тех пор все стандартные алгоритмы в первую очередь ищут use-defined swap и уже на крайняк используют std::swap.
Если ваш класс управлял хоть каким-то ресурсом, даже строкой, вам нужен был свап.
Но по сути-то, свап - это такой одновременный мув друг в друга(идейно). Ну и с появлением мув-семантики стандратная swap стала выглядеть именно так, как нам нужно идейно:
template <typename T>
void swap(T& x, T& y)
{
T temp(std::move(x));
x = std::move(y);
y = std::move(temp);
}
Эта версия свапа делает ровно то, что ожидали практически от всех кастомных swap'ов - эффективный обмен двух значений.
Она позволяет даже некопируемым объектам, типа стримов, мьютексов и прочих, обменяться местами. То есть она буквально отобрала весь хлеб у кастомной swap: теперь стандратная функция делает такой же эффективный обмен значениями, плюс может также обменять некопируемые объекты. Красота!
Но у самописной swap остается одно преимущество: Не происходит никаких вызовов конструкторов классов обмениваемых объектов. Мы напрямую обмениваем содержимое объектов. А std::swap все-таки вызывает один мув-конструктор и 2 мув присваивания. О производительности надо думать...
А еще надо думать об оптимизациях компилятора. Специальные методы могут быть заинлайнены и std::swap превратится ровно в то же, что сгенерирует компилятор для вашей самописной обменивалки.
Также некоторый легаси код может использовать в своих кишках именно метод swap, поэтому чтобы пользоваться этим кодом, нужно реализовывать метод. Но это не то, что бы частая история.
Итог какой: кастомный своп был придуман, в основном, чтобы эффективно обменивать объекты. std::swap на стероидах мув-семантики позволяет делать это очень эффективно. Самописный своп имеет на первый взгляд незначительные преимущества по производительности. Но на практике как всегда надо тестировать оба варианта. Ну или не заниматься преждевременной оптимизацией и использоват std::swap.
Use standard things. Stay cool.
#cppcore #cpp11
🔥24👍11❤5😎1
Вызываем оператор индексации у указателей
Есть суперважная проблема, с которой сталкиваются 100% разработчиков в каждой строке своего кода.
Для указателей перегружен оператор квадратные скобки, который сдвигает указатель на величину индекса вправо и разименовывает его.
И вот теперь представим, что у нас есть класс с собственным перегруженным оператором[]. Например, std::deque. У нас каким-то образом в руках появился указатель на экземпляр этого контейнера. Как у него вызвать оператор взятия индекса объекта?
Если сделать так:
то получите огромную простыню нечитаемых ошибок компиляции.
Все потому что
И шо делать?
Так же не напишешь:
Немного не очень опытных разработчиков знают, что можно вызывать операторы явно. Так вот в этом случае как раз можно вызвать оператор[] явно:
Ну то есть все как обычно: есть указатель на объект, мы вызываем у него метод через оператор доступа к членам (для пролетариев - оператор стрелочка). Но этот метод - сам оператор и мы просто вызываем его согласно сигнатуре.
Такой вот прикол. Иногда может выручить.
Однако nobody knows зачем и как вы получили указатель на объект с переопределенным оператором[]. Возможно, вам стоит пересмотреть организацию вашего кода.
Express your thoughts explicitly. Stay cool.
#cppcore
Есть суперважная проблема, с которой сталкиваются 100% разработчиков в каждой строке своего кода.
Для указателей перегружен оператор квадратные скобки, который сдвигает указатель на величину индекса вправо и разименовывает его.
И вот теперь представим, что у нас есть класс с собственным перегруженным оператором[]. Например, std::deque. У нас каким-то образом в руках появился указатель на экземпляр этого контейнера. Как у него вызвать оператор взятия индекса объекта?
Если сделать так:
std::deque<int> * deque_inst = new std::deque<int>{1, 2, 3, 4, 5};
std::cout << deque_inst[4] << std::endl;то получите огромную простыню нечитаемых ошибок компиляции.
Все потому что
deque_inst - это указатель, а deque_inst[4] значит "сдвинь указатель на 4 вправо и разыменуй".И шо делать?
Так же не напишешь:
deque_inst->[3]
Немного не очень опытных разработчиков знают, что можно вызывать операторы явно. Так вот в этом случае как раз можно вызвать оператор[] явно:
std::deque<int> * deque_inst = new std::deque<int>{1, 2, 3, 4, 5};
std::cout << deque_inst->operator[](3)<< std::endl;Ну то есть все как обычно: есть указатель на объект, мы вызываем у него метод через оператор доступа к членам (для пролетариев - оператор стрелочка). Но этот метод - сам оператор и мы просто вызываем его согласно сигнатуре.
Такой вот прикол. Иногда может выручить.
Однако nobody knows зачем и как вы получили указатель на объект с переопределенным оператором[]. Возможно, вам стоит пересмотреть организацию вашего кода.
Express your thoughts explicitly. Stay cool.
#cppcore
👍20🔥6❤🔥5❤2
Фактор загрузки std:unordered_map
#новичкам
Все мы знаем, как растет в размерах вектор с увеличением количества элементов. Может быть мы в конкретном случае не знаем мультипликатор увеличения вектора, но механизм мы понимаем.
Но например, неупорядоченная мапа - немного другой фрукт. За счет того, что мы сами можем предоставить свою хэш-функцию для нее, мы очень сильно можем влиять на поведение контейнера. Буквально все похерить плохим хэшом или сделать все по-красоте. Однако не всегда плохой хэш - действительно очень плохой. Может он и плохонький, но зато очень быстрый. Возможно, за счет этого будет много хэш-коллизий и появится проблема кластеризации. Но нам это может быть и не так важно, если у нас есть действительно быстрый хэш.
Таким образом мы реально влияет, если не на внутреннее устройство контейнера, то на тенденции фактического расположения элементов.
Такая возможность кастомизации должна идти вместе с влиянием на поведение мапы при увеличении размеров.
У вектора есть поле - capacity, которое говорит о том, сколько элементов может вмещать внутренний буффер.
Мапа же за счет своей бакетной структуры может вмещать сколько угодно элементов. Но нам не хотелось бы прийти к ситуации, когда мапа вырождается в набор огромных связных списков. Поэтому для нее также должна быть какая-то эвристика, которая поможет предотвратить такую проблему, своевременно увеличивать размер внутреннего массива-хэш-таблицы и перехэшировать элементы.
Этот метод мапы возвращает ее фактор загрузки, который равен среднему числу элементов в одном бакете aka size() / bucket_count(). Эта та характеристика, которая определяет, когда мапа будет расширяться. Точнее не только она. Нужно же еще пороговое значение, при достижении которого произойдет расширение. А вот и оно.
Максимальный фактор загрузки определяет максимальное среднее число элементов в бакетах, после достижения которого произойдет расширение хэш-таблицы.
И обратите внимание, что мы сами можем влиять на это значение! Реализация безусловно предоставляет свое значение(скорее всего 1). Но с помощью экспериментов со своей хэш-функцией и кастомным лоад фактором, вы можете добиться по-настоящему желаемого поведения этого непростого контейнера.
Stay balanced. Stay cool.
#STL #cppcore
#новичкам
Все мы знаем, как растет в размерах вектор с увеличением количества элементов. Может быть мы в конкретном случае не знаем мультипликатор увеличения вектора, но механизм мы понимаем.
Но например, неупорядоченная мапа - немного другой фрукт. За счет того, что мы сами можем предоставить свою хэш-функцию для нее, мы очень сильно можем влиять на поведение контейнера. Буквально все похерить плохим хэшом или сделать все по-красоте. Однако не всегда плохой хэш - действительно очень плохой. Может он и плохонький, но зато очень быстрый. Возможно, за счет этого будет много хэш-коллизий и появится проблема кластеризации. Но нам это может быть и не так важно, если у нас есть действительно быстрый хэш.
Таким образом мы реально влияет, если не на внутреннее устройство контейнера, то на тенденции фактического расположения элементов.
Такая возможность кастомизации должна идти вместе с влиянием на поведение мапы при увеличении размеров.
У вектора есть поле - capacity, которое говорит о том, сколько элементов может вмещать внутренний буффер.
Мапа же за счет своей бакетной структуры может вмещать сколько угодно элементов. Но нам не хотелось бы прийти к ситуации, когда мапа вырождается в набор огромных связных списков. Поэтому для нее также должна быть какая-то эвристика, которая поможет предотвратить такую проблему, своевременно увеличивать размер внутреннего массива-хэш-таблицы и перехэшировать элементы.
float load_factor() const;
Этот метод мапы возвращает ее фактор загрузки, который равен среднему числу элементов в одном бакете aka size() / bucket_count(). Эта та характеристика, которая определяет, когда мапа будет расширяться. Точнее не только она. Нужно же еще пороговое значение, при достижении которого произойдет расширение. А вот и оно.
float max_load_factor() const;
void max_load_factor( float ml );
Максимальный фактор загрузки определяет максимальное среднее число элементов в бакетах, после достижения которого произойдет расширение хэш-таблицы.
И обратите внимание, что мы сами можем влиять на это значение! Реализация безусловно предоставляет свое значение(скорее всего 1). Но с помощью экспериментов со своей хэш-функцией и кастомным лоад фактором, вы можете добиться по-настоящему желаемого поведения этого непростого контейнера.
Stay balanced. Stay cool.
#STL #cppcore
1🔥17👍8❤5
Виртуальный конструктор
#новичкам
Виртуальные методы - это основа полиморфизма, одного из важнейших концептов объектно-ориентированного программирования, который позволяет нам реализовывать сложные конструкции из классов и строить гибкую-расширяемую архитектуру. Но вот если на секунду задуматься: конструктор - это ведь тоже метод. Можем ли мы сделать конструктор класса виртуальным?
Один из тех самых популярных вопросов на собеседованиях, который не проверяет никаких практически применимых знаний. Он скорее направлен больше на понимание концепции ООП и механизма создания объектов в С++.
Ответ: нет, не можем. Логика тут довольно простая. Виртуальные функции подразумевают собой позднее связывание объекта и вызываемого метода в рантайме. То есть для них нужны объекты(точнее vptr, которых находится внутри них). А объекты создаются в рантайме. И для создания объектов нужны констукторы. Получается, если бы конструкторы были виртуальными, тособака постоянно гналась бы укусить себя за жёпу получился бы цикл и парадокс(фанатам Шарифова посвящается). Нет указателя на виртуальную таблицу - нет виртуальности.
Если более формально и официально, то вот комментарий самого Бъерна по этому вопросу:
Виртуальный вызов — это механизм выполнения работы при наличии частичной информации. В частности, он позволяет нам вызывать функцию, зная только тип базового класса, а не точный тип объекта. Для создания объекта необходима полная информация. В частности, вам необходимо знать точный тип того, что вы хотите создать. Следовательно, «вызов конструктора» не может быть виртуальным.
Однако, нам по сути этого и не нужно. Нам нужен механизм создания объекта, зависящий от типа полиморфного объекта. И у нас такой механизм есть! Называется он фабричным методом. В ту же степь идет и паттерн "метод clone()".
В сущности, фабричный метод позволяет создавать объекты, тип которых зависит от типа объекта, у которого вызывается метод.
Метод clone позволяет создавать объекты именно того класса, который на самом деле лежит под данным указателем или ссылкой.
Выглядит это так:
У класса Фигура есть метод clone, который позволяет скопировать текущий объект в новый объект. Метод create позволяет дефолтно создать объект того же класса.
В класса Circle эти методы переопределяются. Теперь можно не зная точного типа полиморфного объекта вызвать его конструктор по умолчанию и копирования.
В эти методы также можно добавить аргументов, в том числе и полиморфных типов. Главное, чтобы сигнатуры методов в наследниках и базе совпадали.
Можете кстати порассуждать в комментах, как бы выглядели виртуальные конструкторы и код, который бы их использовал.
Use well-known tools for your task. Stay cool.
#interview #cppcore #pattern
#новичкам
Виртуальные методы - это основа полиморфизма, одного из важнейших концептов объектно-ориентированного программирования, который позволяет нам реализовывать сложные конструкции из классов и строить гибкую-расширяемую архитектуру. Но вот если на секунду задуматься: конструктор - это ведь тоже метод. Можем ли мы сделать конструктор класса виртуальным?
Один из тех самых популярных вопросов на собеседованиях, который не проверяет никаких практически применимых знаний. Он скорее направлен больше на понимание концепции ООП и механизма создания объектов в С++.
Ответ: нет, не можем. Логика тут довольно простая. Виртуальные функции подразумевают собой позднее связывание объекта и вызываемого метода в рантайме. То есть для них нужны объекты(точнее vptr, которых находится внутри них). А объекты создаются в рантайме. И для создания объектов нужны констукторы. Получается, если бы конструкторы были виртуальными, то
Если более формально и официально, то вот комментарий самого Бъерна по этому вопросу:
A virtual call is a mechanism to get
work done given partial information.
In particular, "virtual" allows us to
call a function knowing only an interfaces
and not the exact type of the object.
To create an object you need complete
information. In particular, you need to
know the exact type of what you want to
create. Consequently, a
"call to a constructor" cannot be virtual.
Виртуальный вызов — это механизм выполнения работы при наличии частичной информации. В частности, он позволяет нам вызывать функцию, зная только тип базового класса, а не точный тип объекта. Для создания объекта необходима полная информация. В частности, вам необходимо знать точный тип того, что вы хотите создать. Следовательно, «вызов конструктора» не может быть виртуальным.
Однако, нам по сути этого и не нужно. Нам нужен механизм создания объекта, зависящий от типа полиморфного объекта. И у нас такой механизм есть! Называется он фабричным методом. В ту же степь идет и паттерн "метод clone()".
В сущности, фабричный метод позволяет создавать объекты, тип которых зависит от типа объекта, у которого вызывается метод.
Метод clone позволяет создавать объекты именно того класса, который на самом деле лежит под данным указателем или ссылкой.
Выглядит это так:
class Shape {
public:
virtual ~Shape() { } // A virtual destructor
// ...
virtual std::unique_ptr<Shape> clone() const = 0; // Uses the copy constructor
virtual std::unique_ptr<Shape> create() const = 0; // Uses the default constructor
};
class Circle : public Shape {
public:
std::unique_ptr<Shape> clone() const override;
std::unique_ptr<Shape> create() const override;
// ...
};
std::unique_ptr<Shape> Circle::clone() const { return new Circle(*this); }
std::unique_ptr<Shape> Circle::create() const { return new Circle(); }У класса Фигура есть метод clone, который позволяет скопировать текущий объект в новый объект. Метод create позволяет дефолтно создать объект того же класса.
В класса Circle эти методы переопределяются. Теперь можно не зная точного типа полиморфного объекта вызвать его конструктор по умолчанию и копирования.
std::unique_ptr<Shape> ptr = std::make_unique<Circle>();
auto new_obj = ptr->create();
auto copy_obj = ptr->copy();
В эти методы также можно добавить аргументов, в том числе и полиморфных типов. Главное, чтобы сигнатуры методов в наследниках и базе совпадали.
Можете кстати порассуждать в комментах, как бы выглядели виртуальные конструкторы и код, который бы их использовал.
Use well-known tools for your task. Stay cool.
#interview #cppcore #pattern
🔥24👍12❤10