Интересная особенность библиотечных объектов функций заключается в том, что реализация гарантирует их работоспособность с указателями. Вообще, результат сравнения (<, >) двух несвязанных указателей не определен. Но мы можем захотеть отсортировать н-р контейнер указателей по их адресу. Утверждается, что например сравнивать их на < нельзя, а вот std::less гарантирует верную реализацию (не знаю как):
На самом деле у меня получились одинаковые результаты, но это не значит, что первый вариант можно использовать.
Более того, ассоциативные контейнеры используют для упорядочивания своих элементов std::less, поэтому можно определить set из указателей или map с указателем в качестве ключа. А еще std::sort начиная с C++20 использует std::less, а не operator< для сортировки.
Вывод: иcпользуйте std::less и std::greater - так короче и безопасней.
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
#include <string>
int main()
{
std::string s1, s2;
std::vector<std::string*> vp;
vp.push_back(&s1);
vp.push_back(&s2);
vp.push_back(new std::string("memory leak"));
auto vp2 = vp; // copy
std::sort(vp.begin(), vp.end(), [](std::string* l, std::string* r) {
return l < r; // непредсказуемый результат
});
auto print_vector_pointers = [&](std::vector<std::string*> v){
for (auto p : vp) {
std::cout << (size_t)p << " ";
}
std::cout << std::endl;
};
print_vector_pointers(vp);
std::sort(vp2.begin(), vp2.end(),
std::less<std::string*>()); // ok
print_vector_pointers(vp2);
return 0;
}
На самом деле у меня получились одинаковые результаты, но это не значит, что первый вариант можно использовать.
Более того, ассоциативные контейнеры используют для упорядочивания своих элементов std::less, поэтому можно определить set из указателей или map с указателем в качестве ключа. А еще std::sort начиная с C++20 использует std::less, а не operator< для сортировки.
Вывод: иcпользуйте std::less и std::greater - так короче и безопасней.
👍1
Как поймать исключение в другом потоке и передать в текущий?
Использовать std::exception_ptr.
Как видим получить указатель на текущее исключение (даже если мы ловим все с помощью ...), можно вызовом std::current_exception.
Возбудить исключение по указателю можно так:
Использовать std::exception_ptr.
Как видим получить указатель на текущее исключение (даже если мы ловим все с помощью ...), можно вызовом std::current_exception.
Возбудить исключение по указателю можно так:
if (eptr)
std::rethrow_exception(eptr);
🔥6
std::size - это в некотором роде универсальная функция, которая вернет размер переданного интервала.
Ей можно передать любой контейнер, std::string или std:;string_view - для них просто вызовется size().
Также можно передать как C-style массив, так и std::array.
Вобщем, всеядная вещь. Доступна с C++17
Ей можно передать любой контейнер, std::string или std:;string_view - для них просто вызовется size().
Также можно передать как C-style массив, так и std::array.
Вобщем, всеядная вещь. Доступна с C++17
#include <iostream>
#include <vector>
#include <list>
#include <array>
int main()
{
std::vector<int> v = { 1, 2, 3, 4, 5 };
std::cout << std::size(v) << std::endl; // 5
std::list<std::string> list = {"a", "b", "c"};
std::cout << std::size(list) << std::endl; // 3
std::string s = "abc";
std::cout << std::size(s) << std::endl; // 3
std::string_view sv{"Look, ma, I'm modern guy!"};
std::cout << std::size(sv) << std::endl; // 25
std::array<int, 4> sa = {1, 2};
std::cout << std::size(sa) << std::endl; // 4
int a[]{ 1, 2, 3, 4, 5 };
// Старый сишный способ
std::cout << (sizeof(a) / sizeof(a[0])) << std::endl; // 5
// Новый нормальный
std::cout << std::size(a) << std::endl; // 5
// Для массива символов вернет c \0
const char carr[] = "12345";
std::cout << std::size(carr) << std::endl; // 6 не 5!
return 0;
}
👍1
Про глобальные константы ч1.
Казалось бы, что глобальные константы - это такая почти каждодневная вещь и потому в плюсах с этим все должно быть очень просто. Мы часто пишем
Чтобы определить (глобальную) константу типа X мы можем написать:
Если у X есть конструктор по умолчанию, то все отработает отлично, если мы проделаем это в единственном .cpp. Но что будет, если добавить такое определение в заголовок и затем включить в два .cpp файла (см картинку)? Такой случай скомпилируется, но это не определит глобальную константу!
Вместо этого будет 2 глобальные константы. Как так, спросите вы? Дело в том, что директива препроцессора #include тупо вставляет код заголовка в каждый .cpp файл. Так что после работы препроцессора, каждый .cpp файл будет содержать строку
В чем тут проблема?
- порядок инициализации не определен (не всегда проблема)
- тратится больше памяти
- конструктор вызывается дважды (и если он что-то делает, то мы возможно этого не хотели).
А правда ли в последнем пункте? Давайте проверим, вставив вывод в конструктор X:
Если у нас есть 2 .cpp, то вывод будет такой:
Конструктор вызвался дважды 😱.
Казалось бы, что глобальные константы - это такая почти каждодневная вещь и потому в плюсах с этим все должно быть очень просто. Мы часто пишем
const int MAX_SOMETHING вместо скажем 1024, чтобы сделать наш код чище. Однако в реальности определение константы в плюсах может приводить к неожиданному поведению. Но давайте к практике.Чтобы определить (глобальную) константу типа X мы можем написать:
const X x;Если у X есть конструктор по умолчанию, то все отработает отлично, если мы проделаем это в единственном .cpp. Но что будет, если добавить такое определение в заголовок и затем включить в два .cpp файла (см картинку)? Такой случай скомпилируется, но это не определит глобальную константу!
Вместо этого будет 2 глобальные константы. Как так, спросите вы? Дело в том, что директива препроцессора #include тупо вставляет код заголовка в каждый .cpp файл. Так что после работы препроцессора, каждый .cpp файл будет содержать строку
const X x;. То есть у каждого файла своя копия x. В чем тут проблема?
- порядок инициализации не определен (не всегда проблема)
- тратится больше памяти
- конструктор вызывается дважды (и если он что-то делает, то мы возможно этого не хотели).
А правда ли в последнем пункте? Давайте проверим, вставив вывод в конструктор X:
class X
{
public:
X(){ std::cout << "X constructed\n"; }
};
Если у нас есть 2 .cpp, то вывод будет такой:
X constructed
X constructed
Конструктор вызвался дважды 😱.
Про глобальные константы ч2.
Самый простой способ исправить проблему с глобальными константами выше - это использовать inline переменные из C++17.
Мы просто напишем вот так:
Это скажет компилятору не определять объект в каждом файле, а поработать в паре с линкером и положить только один объект в двоичные файлы (как мы и хотели).
При этом inline в данном контексте может запутать, так как inline функции встраиваются в код, а inline переменные как-будто бы нет.
Как результат, конструктор вызовется один раз:
Самый простой способ исправить проблему с глобальными константами выше - это использовать inline переменные из C++17.
Мы просто напишем вот так:
inline const X x;
Это скажет компилятору не определять объект в каждом файле, а поработать в паре с линкером и положить только один объект в двоичные файлы (как мы и хотели).
При этом inline в данном контексте может запутать, так как inline функции встраиваются в код, а inline переменные как-будто бы нет.
Как результат, конструктор вызовется один раз:
X constructed.
❤1
Про глобальные константы ч3.
Константы внутри класса, объявленные как
До С++17 мы использовали весьма унылый паттерн, объявляя
Используя inline, мы можем объявить и определить статическую константу в классе одновременно:
Константы внутри класса, объявленные как
static, имеют ту же область видимости, что и глобальные константы и inline также упростил их определение в C++17. До С++17 мы использовали весьма унылый паттерн, объявляя
static в определении класса (заголовке) и определяя в единственном cpp файле (обычно для класса):// заголовок
class MyClass
{
static const std::string MUFFIN_NAME;
};
// cpp (единственный)
const std::string MyClass::MUFFIN_NAME = "Rum baba";
Используя inline, мы можем объявить и определить статическую константу в классе одновременно:
// заголовок
class MyClass
{
static inline const std::string MUFFIN_NAME = "Rum baba";
};
// cpp - ничего не надо
❤3👌1
Виртуальные функции распознаются во время выполнения, только если вызов осуществляется через указатель или ссылку. В этом случае статический тип отличается от динамического.
Как видим, при вызове по значению, будет использован статический тип, то есть вызовется метод того объекта, тип которого имеет переменная.
#include <iostream>
#include <string>
struct Base
{
virtual void run(const std::string & f) const
{
std::cout << "Base::run() " << f << std::endl;
}
virtual ~Base() = default;
};
struct Derived : public Base
{
virtual void run(const std::string& f) const override
{
std::cout << "Derived::run() " << f << std::endl;
}
};
void f_pointer(const Base* b)
{
b->run("pointer");
}
void f_reference(const Base& b)
{
b.run("reference");
}
void f_value(const Base b)
{
b.run("value");
}
int main()
{
Derived d;
f_pointer(&d); // Derived::run() pointer
f_reference(d); // Derived::run() reference
f_value(d); // Base::run() value
return 0;
}
Как видим, при вызове по значению, будет использован статический тип, то есть вызовется метод того объекта, тип которого имеет переменная.
❤2
Про глобальные константы ч4.
До С++17 для создания одной константы можно использовать ключевое слово extern, такой код мы пишем в заголовке:
Код выше с extern - это объявление, а не определение. А вот с inline это было определение. Объявление говорит, что где-то есть переменная с типом X.
Не смотря на то, что C++ требует уникальное определение объекта, объявлений может быть несколько.
Для того, чтобы использовать x, нам нужно где-то его определить. Это обычно используется в одном из .cpp файлов, в каком именно - решать вам, но лучше в том, где это будет иметь больше смысла.
Так как в заголовке только объявление, то и конструктор не принимает параметров (так как он там не вызывается). Вместо этого конструктор принимает параметры в одном из .cpp.
Важно: определение должно быть точно одно. Если определения нет, то будет "undefined external symbol", если больше одного, то - "duplicate external symbol".
До С++17 для создания одной константы можно использовать ключевое слово extern, такой код мы пишем в заголовке:
extern const X x;
Код выше с extern - это объявление, а не определение. А вот с inline это было определение. Объявление говорит, что где-то есть переменная с типом X.
Не смотря на то, что C++ требует уникальное определение объекта, объявлений может быть несколько.
Для того, чтобы использовать x, нам нужно где-то его определить. Это обычно используется в одном из .cpp файлов, в каком именно - решать вам, но лучше в том, где это будет иметь больше смысла.
Так как в заголовке только объявление, то и конструктор не принимает параметров (так как он там не вызывается). Вместо этого конструктор принимает параметры в одном из .cpp.
Важно: определение должно быть точно одно. Если определения нет, то будет "undefined external symbol", если больше одного, то - "duplicate external symbol".
Есть такой код стайл, например в том же Гугле, писать имя константы с k.
Например, так:
Всегда жутко это бесило, но стало интересно, откуда k? Первый вариант, который пришел в голову, что это константа (ведь Брин русский 😊). Гуглежь показал, что это не так далеко от правды.
На SO пишут, что в ранее популярной (и сейчас встречающейся, привет MS) венгерской нотации в начале имени переменной пишут сокращенный тип, например:
Если же взять для имени константы c, то можно спутать с char или count. Кто-то умный предложил взять k, тк в немецком "konstant" это внезапно константа. Так что к могла бы и из русского придти)
P.S. я бы дописывала в начало const: const int constSize = 10;
Например, так:
const int kDaysInAWeek = 7;
Всегда жутко это бесило, но стало интересно, откуда k? Первый вариант, который пришел в голову, что это константа (ведь Брин русский 😊). Гуглежь показал, что это не так далеко от правды.
На SO пишут, что в ранее популярной (и сейчас встречающейся, привет MS) венгерской нотации в начале имени переменной пишут сокращенный тип, например:
int iSize;
bool bBusy;
void *pFoo; // pointer
char chInitial;
Если же взять для имени константы c, то можно спутать с char или count. Кто-то умный предложил взять k, тк в немецком "konstant" это внезапно константа. Так что к могла бы и из русского придти)
P.S. я бы дописывала в начало const: const int constSize = 10;
Среднее арифметическое двух беззнаковых - тема частая, самый канонически пример - это бинарный (двоичный) поиск:
Но тут есть такая проблемка: результат может переполниться. Например, если целые 32-битные, то среднее mid(0x80000000U, 0x80000000U) вернет 0. Не то, что мы бы хотели.
В случае, если мы знаем какое число больше (а мы часто знаем), среднее можно вычислить без переполнения так:
Есть и другие формулы, для некоторых даже есть патент.
unsigned mid(unsigned low, unsigned high)
{
return (low + high) / 2;
}
Но тут есть такая проблемка: результат может переполниться. Например, если целые 32-битные, то среднее mid(0x80000000U, 0x80000000U) вернет 0. Не то, что мы бы хотели.
В случае, если мы знаем какое число больше (а мы часто знаем), среднее можно вычислить без переполнения так:
unsigned mid(unsigned low, unsigned high)
{
return low + (high - low) / 2;
}
Есть и другие формулы, для некоторых даже есть патент.
👍6🔥2
А сегодня, детишки, я расскажу вам сказку про С++ паттерн под названием Return Type Resolver (переведете сами).
Но давайте начнем с проблемы (вот бы все так делали).
Допустим, нам нужно написать 2 функции конвертирования строки в число, одну для int, вторую для float, пусть так:
Но вот беда в том, что только по типу возвращаемого значения функцию перегрузить нельзя. Ну и ладно, назовем функции по разному и пофиг, хнык-хнык.
Но мы же в сказке и добро должно победить! Решение - параметризовать оператор приведения типа, например вот так:
Алярм: требует C++17.
Но давайте начнем с проблемы (вот бы все так делали).
Допустим, нам нужно написать 2 функции конвертирования строки в число, одну для int, вторую для float, пусть так:
int from_string(const char *str) { return std::stoi(str); }
float from_string(const char *str) { return std::stof(str); } // errorНо вот беда в том, что только по типу возвращаемого значения функцию перегрузить нельзя. Ну и ладно, назовем функции по разному и пофиг, хнык-хнык.
Но мы же в сказке и добро должно победить! Решение - параметризовать оператор приведения типа, например вот так:
class from_string
{
const string m_str;
public:
from_string(const char *str) : m_str(str) {}
template <typename type>
operator type(){
if constexpr(is_same_v<type, float>) return stof(m_str);
else if (is_same_v<type, int>) return stoi(m_str);
}
};
int n_int = from_string("123");
float n_float = from_string("123.111");
Алярм: требует C++17.
👍7🔥2🤔1
Если вы думаете, что паттерн Return Type Resolver из предыдущего поста бесполезный и плохой, то возможно вас удивит, что он может быть использовать для реализации nullptr, чтобы выводить нулевой указатель корректного типа в зависимости от типа переменной, к которой nullptr присваивается.
Простой вариант реализации мог бы выглядеть так:
А вот пример реализации nullptr из llvm (впрочем, код очень старый)
Простой вариант реализации мог бы выглядеть так:
struct nullptr_t
{
void operator&() const = delete; // Can't take address of nullptr
template<class T>
inline operator T*() const { return 0; }
template<class C, class T>
inline operator T C::*() const { return 0; }
};
nullptr_t nullptr;
А вот пример реализации nullptr из llvm (впрочем, код очень старый)
GitHub
libcxx/include/__nullptr at master · llvm-mirror/libcxx
Project moved to: https://github.com/llvm/llvm-project - llvm-mirror/libcxx
❤1
Как все мы знаем, protected используется, чтоб предоставить наследникам доступ к защищенным членам, но закрыть от общего доступа. Но всегда ли производный класс может обратиться к protected членам? Давайте посмотрим на код ниже:
В первой реализации функции f() класс-наследник Derived спокойно может обратиться к защищенному и унаследованному полю _protMember через ссылку на Derived.
А вот во втором методе f() доступ к тому же полю через ссылку на Base уже не доступен - код не скомпилится.
Таким образом, производный член класса (или его friend) может обратиться к защищенным членам базового класса только через объект производного.
class Base
{
protected:
int _protMember;
};
class Derived : public Base
{
private:
void f(Derived& d);
void f(Base& b);
int _privMember;
};
void Derived::f(Derived& d)
{
d._privMember = 0;
d._protMember = 0; // OK
}
void Derived::f(Base& b)
{
b._protMember = 0; // Не скомпилится
}
В первой реализации функции f() класс-наследник Derived спокойно может обратиться к защищенному и унаследованному полю _protMember через ссылку на Derived.
А вот во втором методе f() доступ к тому же полю через ссылку на Base уже не доступен - код не скомпилится.
Таким образом, производный член класса (или его friend) может обратиться к защищенным членам базового класса только через объект производного.
Немного про закрытое (private) наследование.
Спецификатор доступа наследования никак не влияет на возможность производного класса обратиться к членам его собственного прямого базового класса. Приватное наследование на запретит доступ непосредственным (!) детям к protected, любое наследование не разрешит доступ к private. Класс сам определяет права доступа наследникам:
Это все очевидно, но интересен вопрос, а что если добавить в цепочку наследования еще классов с разными доступами наследования:
Как видим, protected так и остался доступен для второго уровня наследника при public наследовании, а вот исправить public наследованием от private наследования уже нельзя - protected член стал безвозвратно недоступным из-за первого private наследования.
Спецификатор доступа наследования никак не влияет на возможность производного класса обратиться к членам его собственного прямого базового класса. Приватное наследование на запретит доступ непосредственным (!) детям к protected, любое наследование не разрешит доступ к private. Класс сам определяет права доступа наследникам:
class Base
{
protected:
int _protMember;
};
class PublicDerived : public Base
{
public:
int f() { return _protMember; } // OK
};
class PrivateDerived : private Base
{
public:
int f() { return _protMember; } // OK
};
Это все очевидно, но интересен вопрос, а что если добавить в цепочку наследования еще классов с разными доступами наследования:
class DerivedFromPublic : public PublicDerived
{
public:
int f2() { return _protMember; } // OK
};
class DerivedFromPrivate : public PrivateDerived
{
public:
int f2() { return _protMember; } // NOT OK
};
Как видим, protected так и остался доступен для второго уровня наследника при public наследовании, а вот исправить public наследованием от private наследования уже нельзя - protected член стал безвозвратно недоступным из-за первого private наследования.