C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц
Шрифт:
Интервал:
Закладка:
struct person {
string name;
size_t age;
person(string n, size_t a)
: name{move(n)}, age{a}
{ cout << "CTOR " << name << 'n'; }
~person() { cout << "DTOR " << name << 'n'; }
};
3. Определим общие указатели, которые имеют корректные типы, чтобы указывать на переменные-члены name и age экземпляра класса person:
int main()
{
shared_ptr<string> shared_name;
shared_ptr<size_t> shared_age;
4. Далее войдем в новую область видимости, создадим объект типа person и позволим общему указателю управлять им:
{
auto sperson (make_shared<person>("John Doe", 30));
5. Затем позволим первым двум общим указателям указывать на его члены name и age. Прием, который мы задействуем, заключается в использовании конкретного конструктора типа shared_ptr, который принимает общий указатель и указатель на член общего объекта. Таким образом можно управлять объектом, не указывая на него самого!
shared_name = shared_ptr<string>(sperson, &sperson->name);
shared_age = shared_ptr<size_t>(sperson, &sperson->age);
}
6. После выхода из области видимости выведем на экран значения переменных name и age. Это возможно только в том случае, если память для объекта все еще выделена.
cout << "name: " << *shared_name
<< "nage: " << *shared_age << 'n';
}
7. Компиляция и запуск программы дадут следующий результат. Из сообщения деструктора мы видим, что объект все еще жив и память для него выделена, когда мы получаем доступ к значениям переменных name и age с помощью указателей на члены!
$ ./shared_members
CTOR John Doe
name: John Doe
age: 30
DTOR John Doe
Как это работает
В этом разделе мы сначала создали общий указатель, управляющий объектом person, память для которого выделяется динамически. Затем создали два других умных указателя, указывающих на объект типа person, но не на сам объект, а на его члены name и age.
Чтобы подытожить созданный сценарий, взглянем на рис. 8.4.
Обратите внимание: shared_ptr1 указывает на сам объект person, а shared_name и shared_age — на члены name и age того же объекта. По всей видимости, они будут управлять всем жизненным циклом объекта. Это возможно потому, что указатели внутреннего блока управления все еще ссылаются на тот же блок управления, независимо от того, на какой подобъект указывают отдельные общие указатели.
В этом сценарии счетчик использования блока управления равен 3. Таким образом, объект типа person не будет удален при уничтожении shared_ptr1, поскольку другие общие указатели все еще владеют объектом.
При создании подобных экземпляров общих указателей, указывающих на члены общего объекта, синтаксис выглядит несколько странно. Чтобы получить экземпляр типа shared_ptr<string>, который указывает на член name общего экземпляра типа person, нужно написать следующий код:
auto sperson (make_shared<person>("John Doe", 30));
auto sname (shared_ptr<string>(sperson, &sperson->name));
Чтобы получить указатель на конкретный член общего объекта, мы создаем экземпляр общего указателя, чей тип специализирован для того члена, к которому нужно получить доступ. Именно поэтому используем конструкцию shared_ptr<string>. Затем в конструкторе сначала предоставляем оригинальный общий указатель, сопровождающий объект типа person, а в качестве второго аргумента — адрес объекта, которым будет пользоваться новый общий указатель при разыменовании.
Генерируем случайные числа и выбираем правильный генератор случайных чисел
Чтобы получить случайные числа, программисты С++ до появления С++11 обычно просто использовали функцию rand() из библиотеки C. Начиная с C++11, в нашем распоряжении целый арсенал генераторов случайных чисел, они служат для разных целей и имеют различные характеристики.
Эти генераторы не говорят сами за себя, так что в данном разделе рассмотрим их все. Мы увидим, чем они отличаются, научимся выбирать правильный и узнаем, что, скорее всего, никогда не будем ими пользоваться.
Как это делается
В этом примере мы реализуем процедуру, которая выводит на экран гистограмму, содержащую числа, создаваемые генератором случайных чисел. Затем запустим все генераторы случайных чисел, доступные в STL, и воспользуемся нашей процедурой, чтобы исследовать результаты. Данная программа содержит множество повторяющихся фрагментов, поэтому может быть полезным просто скопировать исходный код из репозитория, дополняющего эту книгу, а не писать повторяющийся код вручную.
1. Сначала включим все заголовочные файлы, а затем объявим об использовании пространства имен std по умолчанию:
#include <iostream>
#include <string>
#include <vector>
#include <random>
#include <iomanip>
#include <limits>
#include <cstdlib>
#include <algorithm>
using namespace std;
2. Затем реализуем вспомогательную функцию, которая позволит сопровождать и выводить на экран статистику для каждого типа генераторов случайных чисел. Она принимает два параметра — количество сегментов и количество образцов. Мы сразу увидим, для чего они нужны. Тип генератора случайных чисел определяется с помощью шаблонного параметра RD. Первое, что мы сделаем в этой функции, — определим псевдоним типа для полученного численного типа чисел, возвращаемых генератором. Кроме того, убедимся, что у нас есть как минимум десять сегментов:
template <typename RD>
void histogram(size_t partitions, size_t samples)
{
using rand_t = typename RD::result_type;
partitions = max<size_t>(partitions, 10);
3. Далее создадим экземпляр генератора типа RD. Затем определим переменную-делитель с именем div. Все генераторы случайных чисел создают случайные числа в диапазоне от 0 до RD::max(). Аргумент функции partitions позволяет вызывающей стороне выбирать, на сколько сегментов мы разделим каждый диапазон случайных чисел. Разделив наибольшее возможное значение на количество сегментов, мы узнаем, насколько большим является каждый из них:
RD rd;
rand_t div ((double(RD::max()) + 1) / partitions);
4. Создадим вектор переменных-счетчиков. Он будет иметь размер, равный количеству сегментов. Затем получим случайные значения от генератора в количестве, равном значению переменной samples. Выражение rd() получает случайное число от генератора и изменяет его внутреннее состояние так, чтобы подготовить его к выдаче следующего случайного числа. Разделив каждое случайное число на div, мы получим номер сегмента, в который оно попадает, и можем увеличить соответствующий