Маша С++
104 subscribers
54 photos
2 files
65 links
Учу C++ и не только вместе с вами, мои котятки)
Download Telegram
std::distance(_InIt first, _InIt last) возвращает "расстояние" - число переходов от первого итератора ко второму.

Для итераторов со случайным доступом (random access), например итератором для vector, эта функция будет работать за O(1) и вернет их разность. Казалось бы, мы можем просто сами найти эту разность итераторов и дело с концом, зачем вообще нужна эта функция?

Однако, для итераторов без случайного доступа (например list), расстояние тоже найдется, но уже путем линейного прохода от одного итератора к другому. Просто разность итераторов тут найти не получится, так как для таких итераторов она просто не определена. Время работы функции в таком случае будет O(n). Таким образом std::distance позволяет найти расстояние по любому типу итераторов, как и многое в std.

Пример:
#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <map>

int main()
{
std::vector<int> v = {10, 20, 30, 40, 50};

std::cout << std::distance(v.begin(), v.end())
<< " " << v.end() - v.begin()
<< std::endl; // 5 5

std::cout << std::distance(
std::next(v.begin()), // ++
std::prev(v.end()) // --
) << std::endl; // 3

auto it = v.begin();
std::advance(it, 3); // продвинемся на 3 вперед
std::cout << std::distance(v.end(), it) << std::endl; // -2

std::list<int> list = { 5, 6, 7, 8 };
std::cout << std::distance(list.cbegin(), list.cend()) << std::endl; // 4

//std::cout << list.end() - list.begin(); // нельзя

std::cout << std::distance(list.cend(), list.cbegin()) << std::endl; // 1 - неверно, тк не random access, UB

std::map<int, int> m = { {1,2}, {3,4}, {5, 6} };
std::cout << std::distance(m.find(3), m.end()) << std::endl; // 2

return 0;
}

// Output:
// 5 5
// 3
// -2
// 4
// 1
// 2


Интересно, что std::distance может вернуть отрицательное значение для random access, если первый итератор дальше второго, что является вполне валидным:
std::distance(v.end(), v.begin()) // -v.size()


Для не random access у меня вернулась ерунда, хотя в таком случае вообще будет UB.
Вспоминаем лямбды)

Неименованный объект-лямбда концептуально (!) трансформируется примерно в код на картинке (имя будет другое и разное для разных лямбд) и так все становится немного понятнее.

Обращаю внимание на const у оператора, это позволит понять, зачем у лямбды может понадобиться mutable.
👍1
Класс, перегружающий оператор вызова (operator() - круглые скобки), позволяет использовать объекты этого типа как функции. Во всем остальном такие классы являются обычными - могут содержать другие методы, хранить состояние и тп.

Напишем в качестве примера класс, который выводит строки в поток через разделитель (пробел по умолчанию). Обращаю внимание, что при перегрузке оператора вызова первые скобки пишутся в названии оператора, а вторые принимают аргументы функции:
#include <iostream>
#include <string>
#include <iomanip>
#include <vector>
#include <algorithm>

class StringPrinter
{
public:
StringPrinter(
const std::string& delim = " ",
std::ostream& stream = std::cout)
: _d(delim), _s(stream)
{

}
~StringPrinter()
{
_s << std::endl;
}
void operator()(const std::string& str)
{
if (_first) {
_first = false;
} else {
_s << _d;
}
_s << std::quoted(str);

}
std::string delim() const
{
return _d;
}
private:
std::string _d;
std::ostream& _s;
bool _first = true;
};

int main()
{
{
StringPrinter print;

print("a");
print("b");
print.operator()("c");
} // "a" "b" "c"

std::vector<std::string> vs = {
"How", "long", "are", "you", "going",
"to", "wait", "before", "you", "demand",
"the", "best", "for", "yourself?"};

std::for_each(vs.cbegin(), vs.cend(), StringPrinter("_"));

return 0;
}

После создания объекта StringPrinter print мы можем "вызвать" его с помощью круглых скобок, а также напрямую print.operator()("c").

Объекты-функции часто используют как аргументы обобщенных алгоритмов, хотя зачастую проще использовать лямбды.
👍1
В заголовке <functional> описаны различные встроенные функциональные объекты.

* Арифметические:
- plus (x + y)
- minus (x - y)
- multiplies (x * y)
- divides (x / y)
- modulus (x % y)
- negate (-x)

* Сравнения (наверное, самые используемые):
- equal_to (x == y)
- not_equal_to (x != y)
- greater (x > y)
- less (x < y)
- greater_equal (x >= y)
- less_equal (x <= y)

* Логические
- logical_and (x && y)
- logical_or (x || y)
- logical_not (!x)

* Битовые и другие

Рассмотрим распространенный пример - сортировку. По-умолчанию, std::sort будет выполнять сортировку по возрастанию, что обеспечит вызов operator<.
Если же мы хотим отсортировать по убыванию (макс. вперед), то можно воспользоваться функциональным объектом std:;greater. На самом деле это шаблонная структура с перегруженным оператором вызова (реализует >), но начиная с C++ 14 тип можно не указывать:
template< class T = void > 
struct greater;


Пример:
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>

int main()
{
std::vector<int> v1 = { 5, 1, 2, 4, 3 };
auto v2 = v1; // сделаем копию

std::sort(v1.begin(), v1.end()); // 1 2 3 4 5

for (auto i : v1)
std::cout << i << " ";
std::cout << std::endl;

std::sort(
v2.begin(),
v2.end(),
std::greater()); // 5 4 3 2 1

for (auto i : v2)
std::cout << i << " ";
std::cout << std::endl;

return 0;
}
👍4
Интересная особенность библиотечных объектов функций заключается в том, что реализация гарантирует их работоспособность с указателями. Вообще, результат сравнения (<, >) двух несвязанных указателей не определен. Но мы можем захотеть отсортировать н-р контейнер указателей по их адресу. Утверждается, что например сравнивать их на < нельзя, а вот std::less гарантирует верную реализацию (не знаю как):

#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.

Возбудить исключение по указателю можно так:
        if (eptr)
std::rethrow_exception(eptr);
🔥6
std::size - это в некотором роде универсальная функция, которая вернет размер переданного интервала.
Ей можно передать любой контейнер, 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
Моя скромная коллекция книг по C++, делитесь вашей в комментах 😊
#books
🔥51👍1
Продолжаем с лямбдами
#lambda
👍2🔥1
👍2
Про глобальные константы ч1.

Казалось бы, что глобальные константы - это такая почти каждодневная вещь и потому в плюсах с этим все должно быть очень просто. Мы часто пишем 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

Конструктор вызвался дважды 😱.
(если что, то X const x и const X x - одно и тоже)
Про глобальные константы ч2.

Самый простой способ исправить проблему с глобальными константами выше - это использовать inline переменные из C++17.
Мы просто напишем вот так:
inline const X x;

Это скажет компилятору не определять объект в каждом файле, а поработать в паре с линкером и положить только один объект в двоичные файлы (как мы и хотели).

При этом inline в данном контексте может запутать, так как inline функции встраиваются в код, а inline переменные как-будто бы нет.

Как результат, конструктор вызовется один раз:
X constructed
.
1
2
Про глобальные константы ч3.

Константы внутри класса, объявленные как 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 const X x;


Код выше с extern - это объявление, а не определение. А вот с inline это было определение. Объявление говорит, что где-то есть переменная с типом X.

Не смотря на то, что C++ требует уникальное определение объекта, объявлений может быть несколько.

Для того, чтобы использовать x, нам нужно где-то его определить. Это обычно используется в одном из .cpp файлов, в каком именно - решать вам, но лучше в том, где это будет иметь больше смысла.

Так как в заголовке только объявление, то и конструктор не принимает параметров (так как он там не вызывается). Вместо этого конструктор принимает параметры в одном из .cpp.

Важно: определение должно быть точно одно. Если определения нет, то будет "undefined external symbol", если больше одного, то - "duplicate external symbol".
Есть такой код стайл, например в том же Гугле, писать имя константы с k.
Например, так:
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;
Среднее арифметическое двух беззнаковых - тема частая, самый канонически пример - это бинарный (двоичный) поиск:
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