Динамо-машины  Метод Сократа 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 [ 49 ] 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

Таблица 21.3. Реальные расходы памяти на хранение объекта, рассматриваемые в предположении, что sizeof (string) == 16, указатели и int имеют размер 4 байта, и выделение памяти происходит 16-байтовыми блоками

Контейнер

Теоретический размер данных узла

Реальный размер блока выделенной для узла памяти (с учетом выравнивания и накладных расходов при вьщелении памяти)

list lt;char gt;

9 байтов

16 байтов

set lt;char gt;, mu1tiset lt;char gt;

13 байтов

16 байтов

list lt;int gt;

12 байтов

16 байтов

set lt;int gt;, multiset lt;int gt;

16 байтов

16 байтов

1ist lt;stri ng gt;

24 байта

32 байта

set lt;string gt;, multiset lt;stri ng gt;

28 байтов

32 байта

В табл. 21.3 приведены результаты простого анализа с использованием рассмотренных выше предположений. Вы можете повторить эксперимент на своей платформе со своим компилятором - просто подставив собственные значения. Чтобы написать программу, которая определяет реальные накладные расходы при выделении блока определенного размера на вашей платформе, обратитесь к приложению 3 в книге Джона Бентли (Jon Bentley) [BentleyOO].

Благодаря табл. 21.3 мы немедленно обнаруживаем один интересный результат: во многих случаях - примерно для 75% всех возможных размеров типа т - накладные расходы при использовании 1 i st и set/multiset в расе м атр и ва с м о й среде оказываются одинаковы. Более того, вот результат, который еще интереснее, - list lt;char gt; и set lt;int gt; имеют в данной среде одни и тс же реальные накладные расходы, несмотря на то, что последний контейнер хранит данные большего размера и требует большего количества дополнительной информации для каждого узла.

Если используемое количество памяти является важным фактором при выборе вами структуры данных в конкретной ситуации, потратьте несколько минут на проведение подобного анализа для вашей системы и посмотрите на реальные различия в потреблении памяти разными контейнерами - иногда эти различия гораздо меньше, чем вы думаете!

Резюме

Для каждого вида контейнера определяются свои компромиссы между расходами памяти и производительностью. При использовании vector и set можно быстро решить те задачи, которые невозможно столь же эффективно решить при использовании 1 ist - например, поиск за время 0(log N); при использовании vector можно сделать веши, невозможные при применении list или set - например, произвольный доступ. Вставка элемента в средину легко выполняется в 1 i st, менее эффективно-в set, и очень медленно - в vector. Словом, примеров таких по-разному выполняющихся задач очень много. Большая гибкость зачастую требует больших накладных расходов памяти, но если учесть выравнивание данных и возможные стратегии распределения памяти, то различие может оказаться куда меньшим, чем вы думаете! Вопросы выравнивания данных и оптимизации использования памяти рассматривались также в книге [SutterOO].

Если содержимое вектора отсортировано. Задача 21. Контейнеры в памяти. Часть 2: какие они на самом деле?



gt; Рекомендация

Следует четко представлять, к каким реальным расходам памяти приводит использование различных видов контейнеров и стратегии распределения динамической памяти.



Задача 22, Новый взгляд на new.

Часть 1: многоликий оператор new Сложность: 4

Любой класс, предоставляющий собственный оператор new или new[], должен также обеспечить соответствующие версии обычного оператора new, размещающего new и new, не генерирующего исключений. В противном случае у пользователей вашего класса могут возникнуть ненужные проблемы.

Вопрос ДЛЯ новичка

1. Какие три варианта оператора new описаны в стандарте С++? Вопрос для профессионала

2. Что такое оператор new, специфичный для класса, и как им следует пользоваться? Опишите, в каких случаях вы должны быть особо осторожны при предоставлении собственных, специфичных для класса операторов new и delete.

3. Какой именно оператор new вызывается в приведенном далее коде в строках, пронумерованных от 1 до 4?

class Base {

public:

static void* operator new(std::si2e t, const FastMemoryA);

class Derived : public Base {

...

Derived* pi = new Derived; 1

Derived* p2 = new (std::nothrow) Derived; 2

void* p3 = /* Некоторая область памяти, достаточная

для размещения Derived */ ; new (рЗ) Derived; 3

FastMemory f;

Derived* p4 = new (f) Derived; 4

Решение

в этой и следующей задачах я хочу сформулировать и обосновать два основных совета.

Любой класс, предоставляющий собствснный оператор new или new[], должен также обеспечить соответствующие версии обычного оператора new, размещающего new и new, не генерирующего исключений. В противном случае у пользователей вашего класса могут возникнуть ненужные проблемы.

Избегайте использования new(nothrow) и убедитесь, что, когда вы проверяете отказ new, вы действительно проверяете именно то, что хотите проверить.

Советы могут показаться неожиданными, поэтому давайте детально их проанализируем. Для простоты я не упоминаю отдельно оператор new для массивов, но все сказанное об операторе new для одного объекта, относится и к нему.



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 [ 49 ] 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90