Pull to refresh

Comments 53

Проблему с безымянными указателями можно решить с помощью std::make_shared
Кстати ценное замечание, спасибо. Что-то я об этом забыл упомянуть.
Не можно, а нужно. Вообще, везде, где есть возможность, стоит использовать std::make_shared. Странно что в статье про это нет ни слова.
Главная проблема тут в том, что пользоваться weak_ptr практически никогда нельзя. В любой мало-мальски сложной программе, в которой нельзя однозначно построить дерево владения объектами друг друга, тем более в многопоточной, weak_ptr может протухнуть в любой, самый неожиданный момент. Поэтому, прежде чем работать с объектом по weak_ptr, обязательно нужно сначала создать временный shared_ptr (и не забыть убить его в конце работы). Определить конец работы легко, если последовательность операций представляет собой дерево. А вот на графе (асинхронно-многопоточный код) это в общем случае невозможно.
То есть, ещё раз, для графа shared/weak указатели не работают. Не работает сам алгоритм подсчета ссылок. А для случая дерева они не нужны, там достаточно обычных динамических или даже локальных переменных.
Процитирую свой же, заминусованный комментарий.
Weak_ptr? Они что, шутят? С третьей, или какой там по счету, попытки сделать в STL автоматическое управление памятью, предлагается weak_ptr? Годика через три вопрос «приведите пример, когда нужен weak_ptr» будет встречаться на собеседованиях с такой же частотой, как и вопрос про виртуальное базовое наследование. И с такой же частотой употребляться на практике. Потому что это еще один, тысячный способ гарантированно прострелить себе ногу. А без weak_ptr не будет нормально работать shared_ptr. Ну и зачем тогда всё это?
Посмотрите на Objective-C — язык полностью построенный на модели подсчета ссылок. И нет никаких проблем (на самом деле есть, но не те, что описаны вами) с многопоточностью. И там weak ссылки работают отлично, во многих местах. Там все просто — создаете strong ссылку из weak для скоупа, в котором работаете. Дя этого shared_ptr / weak_ptr и разрабатывались.
Это всё здорово, когда есть строго очерченный скоуп. Т.е. дерево управления.
Во многих программах — сетевых демонах, сервисах, гуёвых приложениях дерева нет. Есть граф. Вот создался/загрузился объект. Потом пришло событие, потом другое событие к тому же объекту, потом третье. Обработчики событий могут порождать длительные асинхронные обращения к той же базе данных, например, или к worker threads. Порядок и даже сам факт прихода этих событий вам неизвестен. Будете использовать weak_ptr — получите тухлые указатели. Будете использовать shared — получите out_of_memory.
Ну наверное не зря во всяких java и php для управления памятью используют алгоритмы, основанные на анализе достижимости вершин графа.
Obj-C используется как раз в гуёвых приложениях, и проблем в графом объектов (именно, тех, которые описали вы) не наблюдается.
Будете использовать weak_ptr — получите тухлые указатели. Будете использовать shared — получите out_of_memory.
Использование только сильных/слабых ссылок вместо использования голых указателей дает возможность уйти от мыслей о работе с памятью к мыслям о органицации графа зависимостей.
Предположим, что объект A имеет слабую ссылку на B. В методе A из слабой ссылки (weak_ptr) создается сильная ссылка(shared_ptr), и внутри скоупа этого метода идет работа с сильной ссылкой — она точно не протухнет, пока shared_ptr жив. Эту ссылку можно передавать в другие методы, даже асинхронные, и объект A будет жить пока это необходимо этому асинхронному методу (например).
Системе подчета ссылок не одно десятилетие (Cocoa/Objective-C, Delphy), и у нее есть свои недостатки, но я очень рад, что она наконец-то появилась и в C++11.
Так.
Давайте смотреть на проблему в целом.
Проблема такая: есть ссылающиеся друг на друга объекты, которые образуют циклический граф, причём этот граф динамически изменяется в процессе выполнения программы. Необходимо своевременно удалять те объекты, у которых не осталось путей к некоторому объекту-корню.

В случае с weak/shared указателями для решения этой задачи предлагается использовать алгоритм подсчета ссылок. Но так как этот алгоритм работает только с деревьями и не работает с циклическими графами, предлагается обязать программиста строить на этом графе остовное дерево из shared-указателей и объявлять все не попавшие в это дерево связи как weak указатели. И не только строить, но и всегда поддерживать это дерево в корректном состоянии.
Цена ошибок в поддержании корректности дерева варьируется от очень неприятных (неудаляемая память в случае появления лишних shared_ptr, образующих цикл) до фатальных (работа с протухшим weak_ptr портит память, что может всплыть неизвестно где, а потеря одного shared_ptr близко к корню вызывает каскад деструкторов по всему графу). В цикломатически сложных многопоточных программах подобные ошибки могут возникать только при определенных фазах луны при сатурне в третьем доме и относятся к категории не повторяемых и совершенно не отлаживаемых. Алгоритм, провоцирующий фатальные, неповторяемые, не отлаживаемые ошибки сложно назвать хорошим, не правда ли?
Ситуация ухудшается тем, что не существует какого-либо единственного способа построить остовное дерево на произвольном графе. Это значит, что программист должен постоянно держать в уме, где у него shared-рёбра, а где weak, и не допускать совершенно никаких ошибок. А что, если программистов много? А что, если часть библиотек идёт без исходников, и неизвестно, что у них там внутри? Волна деструкторов ведь может прийти и из сторонней либы, опять же при сатурне в третьем доме.
И всё из-за того, что мы изначально используем для графа алгоритм, предназначенный только для дерева.

Окай, во всяких java используются алгоритмы непосредственно для графов, основанные на выявлении компонентов связности. Но так как граф динамически изменяется, тут тоже есть свои подводные камни, а именно:
* фризы (замораживание всех потоков) на время работы сборщика мусора
* перерасход памяти в ситуации, когда объекты уже удалены, но сборщик еще не запустился
* немасштабируемость на большие графы (Завалишин писал, что фриз для всей операционной системы фантом занимает несколько часов).
Поэтому появились сложные многопроходные алгоритмы, не требующие или почти не требующие фризов, зато не гарантирующие какого-либо определенного максимального времени жизни объектов после объявления их ненужными.

Теперь внимание, правильный ответ для большинства случаев.
В большинстве случаев у нас есть точка, когда уже совершенно точно можно объявлять объекты подлежащими удалению. Запрос полностью обработан, юзер закрыл документ, геймер прошел уровень, клиент закрыл соединение и пр.
Вместо плясок с графом мы складываем все связанные с запросом объекты в отдельный пул временных объектов. Затем, после точки Х, мы просто удаляем сразу все объекты из этого пула, не разбираясь с их внутренними взаимосвязями.
Всё.
Единственное ограничение — все эти объекты должны влезать в память одновременно. Лечится введением подзапросов со своими собственными пулами.
Давайте-же смотреть на проблему в целом.

Давайте, представим для простоты, что граф наших объектов — это дерево, с жесткими ссылками (и, слабыми/weak обратными ссылками). Варианты, когда листья дерева могут ссылаться на другие листья пока не рассматриваем.
shared(strong) ссылки реализуют семантику владения без необходимости вызова деструктора самостоятельно.

Основная проблема с голыми указателями — это определить эту самую семантику владения — кто должен вызывать delete/delete[]? shared_ptr/weak_ptr как раз указывают явно эту семантику в коде.

Вернемся к нашему примеру. У нас есть «дерево» объектов. Мы решаем, что нам нужно сделать что-то в другом потоке в каким-то поддеревом. Мы передаем в другой поток это поддерево, скопировав shared_ptr. Тем самым мы разделили владение этим поддеревом с другой сущностью (потоком). Теперь, предположим наступает тот самый момент,
когда уже совершенно точно можно объявлять объекты подлежащими удалению
Мы отказываемся от владения вершиной дерева. Если никто не завладел вершиной дополнительно, то дерево будет удалено, кроме поддерева, которым совладеет другой поток. Это поддерево останется валидным, все объекты живы пока им владеет другая сущность. Теперь поток что-то сделал, как-то преобразовал это поддерево и решил оповестить о этом «надкорень» этого дерева. он попытается получить сильную ссылку из слабой и узнает, что объекта уже нет. Все. Он может остановится и не делать ничего. Поток не упадет только лишь потому, что кто-то где-то решил, что час Ч настал и можно удалять всё дерево.

Заметтьте, в описаном примере никто не вызывает деструктор. Это не нужно. Нужно всего лишь отказаться от владения. Программируя я не хочу думать когда и кто должен будет вызывать конструкторы/деструкторы (в идеале). Я хочу описывать задачу на более высоком уровне. Я хочу сказать «на момент вызова этого метода» (или «пока жив этот объект») такие-то данные должны оставаться валидными. И это все. Когда мне данные перестают быть нужны я хочу просто отказаться от владения ими. Эту семантику владения и описывают strong/weak умные указатели.

Описанный вами пулл — это лишь одно из возможных решений. И не всегда выгодное. Например, если данные, с которыми мы работаем иммутабельны, то shared_ptr будет сильно выигрывать по памяти, поскольку не потребуется их копировать в каждый из N пуллов, будут созданы только N shared_ptr, указывающие на одну область памяти.
1. Ну, и? Где продолжение-то? Вы предположили, что граф ваших объектов это дерево, для простоты. Хорошо, в простом случае shared/weak работают. Я даже согласен признать, что синтаксически они слаще, чем ручной вызов new/delete (хотя для дерева и они тоже работают довольно очевидным образом, нужна только аккуратность).
Теперь допустим, что в версии 2.0 в вашу программу добавили еще один тип объектов, и дерево перестало быть деревом, а стало нормальным циклическим графом, и никаким очевидным образом к дереву теперь не сводится. И?

2. Пул объектов не является серебряной пулей, главный его недостаток — неуниверсальность. Именно поэтому он не используется в managed-языках, где с программиста хотели снять вообще все заботы о памяти. Но в плюсах, где программист может выбирать калибр для стрельбы в свою ногу, было бы логичным ввести эту технику на уровне языковых конструкций, а не на уровне STL (чтобы ей штатно пользовались все либы, а не так, что здесь std::shared, там boost::shared, а там mybicycle::shared).
Давайте пример.
Классическая триада Модель (объект) — Представление (гуй) — Операция (некая длительная обработка модели, с превью текущих результатов в гуе).
Между всеми тремя отношения многие-ко-многим (в одном представлении или операции может использоваться несколько разных объектов, к одному объекту может быть прицеплено несколько гуев и т.п.). В том числе допустимы ситуации с нулём (не ко всем объектам/операциям прицеплен гуй).
Гуй и операции, разумеется, выполняются в разных потоках.
Ну попробуйте разрулить это при помощи shared/weak.
Связи в направлении «представления — операции — модели» — сильные. Связи в обратном направлении — слабые.

Внутри каждой группы объекты образуют лес и обрабатываются согласно правилам леса.

Представлениями исходно владеет оконная библиотека. Также любые объекты удерживаются при помощи локальных переменных пока исполняются. Что я забыл?..
Граф есть хаос, дерево рождает порядок.
Зачастую лучше представить граф в виде набора деревьев. Так программы получаются надежнее.
«Перекрестные ссылки» и «кольцевые ссылки» имеют вполне устоявшийся в русском языке термин «Циклические ссылки» (от англ. retain cycle).
Описанный в статье простой и очевидный вид циклических ссылок. Гораздо более часто встречающийся в реальных приложениях, и гораздо более трудно находимые — это когда объект A имеет жесткую ссылку на B, который имеет жесткую ссылку на С, который имеет жесткую ссылку на A.
Спасибо за комментарий. Абсолютно согласен, что в реальном коде циклические ссылки встречаются в гораздо более «извращенных формах». Боюсь, что упоминание о более сложных случаях, серьезно усложнило бы материал и увеличило бы объем.
Честно говоря, пункт «Проблема использования в разных потоках», где read и write, выглядит как явный косяк реализации shared_ptr. Имхо, эту проблему должны были решить разработчики shared_ptr, потому что заставлять юзеров вешать мьютекс на shared_ptr, внутри которого тоже мьютекс — это изврат какой-то. Вообще, слабо верится. Обещали же вроде многопоточность в документации. Надо будет проверить.
Речь в статье про один и тот же экземпляр shared_ptr. Если shared_ptr скопировать, то внутри одного потока его можно свободно использовать и копировать дальше. Атомарный счетчик именно этой цели и служит. Реализация с мутексом была бы очень тяжелой по производительности.
Я тогда не очень понимаю смысл «многопоточности» shared_ptr, если его всё равно надо закрывать мьютексом. Приведите, пожалуйста, пример, что можно делать с shared_ptr из нескольких потоков без дополнительного мьютекса.
Безопасность shared_ptr заключается в безопасности манипуляций над отдельными экземплярами shared_ptr, пусть даже имеющими ссылку на один и тот же блок подсчёта ссылок(который и защищён атомарностью). Т.е. пользователя не должна заботить деталь реализации блока подсчёта ссылок. Он работает так, как-будто никакого разделяемого блока нет и в помине. По моему мнению, автор привёл немного неудачный пример т.к. эта проблема присуща любому разделяемому ресурсу и shared_ptr выделять смысла нет никакого.
многопоточность нужна для только для эффективности, эффективность и встроенная защита от многопоточности это взаимоисключаюшие параграфы. Поэтому разработчики абсолютно правы, что в эффективый класс не стали добавлять блокировок
1. Имхо сначала бы не мешало почитать доку, никто вам и не обещал, что shared_ptr будет разрешать циклические зависимости (перекрестные ссылки).
2. ну про безымянные уже всё сказано, и для этого создавалось семейство make_xxx функций.
3. опять следовало бы почитать для начала документацию, shared_ptr потокобезопасен тогда, когда объект уже захвачен, reset же это захват нового объекта и она не является потокобезопасной функцией.

Остальные механизмы не использовал, но исходя из того, что по первым 3м почитать доку было лень, боюсь предположить.
Зря Вы так, помимо отсутствия упоминания make_shared содержание заметки выглядит вполне релевантной заголовку. Автор привел примеры подводных камней и пояснил как их обойти. Ещё я бы переписал часть про многопоточность, т.к. не совсем в тему она тут.
Спасибо за комментарий! Это мой первый опыт в таких публикациях. Я решил нужным упомянуть об этой проблеме, т.к. в действительности не все программисты знают в каких случаях атомарный счетчик уберегает от проблем. Я думаю, что мог выразить мысль не совсем внятно. А как именно вы предлагаете изменить часть про многопоточность?
Я бы заострил внимание, что является потокобезопасным в shared_ptr, а факт про reset оставить как дополнение; мол, shared_ptr ничем не отличается от других разделяемых ресурсов и имеет такие-то проблемы. Просто, как мне кажется. не раскрыта тема потокобезопасности shared_ptr. Что же в нём потокобезопасного? Вы справедливо отметили, что в нём не безопасно, но не достаточно уделили внимания безопасной части, по моему мнению.
То есть Вы хотите сказать, что выглядит так, будто постулируется, что все операции для всех экземпляров, указывающих на объект, должны выполняться под локом?

Завтра утром внесу правки, если придумаю. Боюсь разбухания текста. Сокращал, а получилось все равно очень много.
Нет, просто тема потокобезопасности блока подсчёта ссылок упоминается лишь вскользь, зато весь параграф посвящён тому, что справедливо для любого объекта не обладающего потокобезопасными методами. Поэтому мне и не ясно, зачем выделять shared_ptr отдельно. А вот если написать подробнее про потокобезопасность блока ссылок и после этого показать, что это не делает сам shared_ptr потокобезопасным, то это будет полная картина. Я не думаю, что там стоит много писать. Просто добавить пару предложений, чтобы было понятно, что безопасно, а что нет в sahred_ptr
Это публикация ориентирована на тех кто начал (начинает) работать с данной технологией (как описано во вступлении). Сомневаюсь, что для Гуру она будет полезна. Я не претендую на совершенный разбор «неизведанных глубин».
Вы справедливо заметили, вся эта информация есть в документации и частями в различных публикациях. Я лишь хочу облегчить путь для тех, кто им еще не ходил, чтобы сэкономить им часы отладки. Ведь не все люди никогда не совершают ошибок.

В любом случае, спасибо за Ваше мнение, ведь не получить хотя бы одного негативного отклика было бы подозрительно.
UFO just landed and posted this here
А можно вот это предложение немного детализировать?
«Иногда требуется получить shared_ptr из методов самого объекта. Попытка создания нового shared_ptr от this приведет к неопределенному поведению (скорее всего к аварийному завершению программы), в отличие от intrusive_ptr, для которого это является обычной практикой.»
1) Почему это приводит к неопределенному поведению? 2) В двух словах о intrusive_ptr.
1) Потому что тогда на один объект будет заведено два счетчика
Эквивалентно
Widget* p = new Widget;
shared_ptr<Widget> a( p );
shared_ptr<Widget> b( p );

При выходе второго объекта из области определения будет попытка освобождения уже освобожденного объекта

2) intrusive_ptr содержит встроенный счетчик и поэтому такая конструкция
Widget* p = new Widget;
intrusive_ptr<Widget> a( p );
intrusive_ptr<Widget> b( p );

Уже не приведет к ошибке, т.к. у них остается общий счетчик (встроенный) и при выходе первого объекта(a) из области определения он только уменьшит счетчик, но не удалит объект.

Поэтому для intrusive_ptr валидной является конструкция
intrusive_ptr<Widget>( this );
Что-то про безымянные указатели странно.
foo(bar(new object1()), new object2());
new это фактический вызов обычной функции поэтому «new object1()» и вызов bar не могут быть разделены — это нарушение порядка выполнения операций (см. порядок выполнения операций).
Лишь отсылаю к документации
www.boost.org/doc/libs/1_54_0/libs/smart_ptr/shared_ptr.htm
начиная с «Best Practices».
Там есть ссылка на Сартара

Порядок выполнения действительно не может быть нарушен в том плане, что
1) bar будет вычислен раньше foo
2) new object1 будет вычислен раньше bar
3) new object 2 будет вычислен раньше foo
В остальном как повезет.
То, что аргументы функции могут вычисляться в любом порядке — это ладно. Можно, наверное, даже придумать какие-то оптимизации в этом плане. Но я вообще не представляю, что должно твориться в голове у разработчика компилятора, чтобы начать вычисление первого аргумента функции, выполнить его частично, отложить в сторону, выполнить вычисление второго аргумента функции и затем вернуться к окончанию вычисления первого. Эта схема бъёт по всему — по производительности CPU, по памяти, по попаданиям данных в кеш — кто в здравом уме это сделает в компиляторе?
Хотел ответить однозначно на вопрос, а не выходит, потому как вопрос некорректный (но при этом правильный и вполне логичный). Попробую показать на простом примере:

  1. В коде:
    func(arg1, arg2)
  2. В псевдо-ассемблере на входе оптимизатора:
    mov arg1, r5   // arg1
    mov r1, [addr] // arg2
    mov arg2, [r1] // arg2
    call func
    
  3. В псевдо-ассемблере на выходе оптимизатора:
    mov r1, [addr] // arg2
    mov arg1, r5   // arg1
    mov arg2, [r1] // arg2
    call func
    

Почему? Потому что планировщик инструкций видит две идущие подряд операции работы с памятью и, зная их задержку, перемешивает инструкции для достижения более оптимального использования процессора.

Запретить делать такое компилятору значит не только всё замедлить, но и усложнить жизнь его разработчикам, которым придётся дополнительно аннотировать инструкции. И у этого будут очень даже далеко идущие последствия в плане ограничения применения оптимизаций к выражениям, вычисляющимся в параметрах.

Может будет проще представить вызов функции как последовательное присвоение значений N переменным, а затем сам переход, чем он по сути и является. В этом случае видно, что в вызове нет ничего особенного: компилятор перемешивает инструкции из разных выражений всегда, если это не влияет на результат, и тут происходит то же самое.

Как оно есть сейчас очень даже разумно с точки зрения того, что определяет стандарт, а что реализация. Стандарт просто не может покрывать такие детали, не нанося существенного ущерба в общем случае.
UFO just landed and posted this here

Эта точка следования относится только к вызову new Widget и вызову конструктора shared_ptr<Widget>(...). На вычисление bar() эта точка следования никак не влияет, и bar() может вычисляться когда компилятору угодно.

UFO just landed and posted this here
UFO just landed and posted this here
У вас в разделе «Особенности времени разрушения освобождающего функтора для shared_ptr» первый пример работает, даже если закомментировать строку
// Обратите внимание на следующую строчку
connectionToRelease.reset();

Вот демонстрация: ideone.com/kR7OBv
И я что-то не могу придумать, как ещё надо исправить пример, чтобы ConnectionReleaser не был уничтожен.
Немного изменил свой пример (изначально в нём не было weak_ptr, так как не полностью понял описываемую проблему), но теперь ConnectionReleaser не уничтожается не зависимо от того, есть или нет указанная выше строка, то есть эта строка не влияет на вывод.
Извиняюсь за флуд в комментариях, но я наконец-то разобрался что проблема в том, что если закомментировать указанную строку, то соединение из пула будет продолжать жить, до тех пор, пока не будут уничтожены все weak_ptr, хотя к этому времени пул уже может не существовать. Подводные камни в том, что может ожидаться, что соединения уничтожаются при уничтожении пула, но здесь это оказывается не так.
Абсолютно верно. Вероятнее всего в случае, если reset вызван не будет, ничего страшного не произойдет, но если это вдруг окажется важным для корректного функционирования, то потенциально это может стоить часов отладки.
Кстати полный листинг моего примера есть в приложении. Для этого и некоторых друх случаев
Да, его я видел и тоже запускал: ideone.com/yRpnJJ (под C++11, без Boost)

Кстати, в разделе про enable_shared_from_this тоже не сразу понятны все проблемы.

Во-первых, не ясно в чём могут быть проблемы, если не использовать enable_shared_from_this. А проблемы в том, что можно создать два share_ptr, один где-то во вне, а другой возвращённый методом объекта. Потом будет попытка дважды освободить одну и ту же область памяти.

Во-вторых, про enable_shared_from_this не прописано явно, что до вызова shared_from_this() обязательно должен быть создан shared_ptr (хотя по коду это видно), если этого не сделать, то weak_ptr не инициализируется. Метод init() как раз решает эту проблему, создавая shared_ptr перед вызовом shared_from_this().

В пример можно добавить такой код:
class BadWidget3: public boost::enable_shared_from_this<BadWidget1> {
public:
    BadWidget3() {
    }

   shared_ptr<BadWidget3> f() {
        return shared_from_this();
   }
};

main()
{
    BadWidget3* w = new BadWidget3;
    shared_ptr<BadWidget3> w1 = w->f();
}
Безымянные указатели

Неплохо бы было сразу указать ссылку на источник этого знания. Авторитетный кстати. До Саттера как-то нигде не было и намёка на возможность подобной коллизии.

Проблема использования в разных потоках

Строго говоря это не является проблемой shared_ptr. Это проблема потоконебезопасного использования объекта, тип объекта вторичен.
Но если очень хочется то можно ( из буста ):
template<class T> void atomic_store( shared_ptr<T> * p, shared_ptr<T> r )
{
    boost::detail::spinlock_pool<2>::scoped_lock lock( p );
    p->swap( r );
}

P.S. Именно этот пост побудил меня зарегистрироваться на Хабре, для этого комментария.
Такой вопрос:
А если shared_ptr Ptr( new MyClass[10] );?

Произойдёт ли корректное освобождение памяти и вызовы всех 10 деструкторов ~MyClass при выходе Ptr из области видимости?
Сам отвечу на первый вопрос — корректное освобождение памяти произойдёт по любому (даже если вызвать delete 'указатель на массив' без квадратных скобок происходит освобождение памяти под массив, а вот вызовы всех деструкторов — не факт), а что с shared_ptr — вызовет ли все деструкторы?
В вашем случае shared_ptr «не знает» размер массива, поэтому он никак не сможет вызвать деструкторы.
При создании shared_ptr можно указать свой deliter либо в C++11 есть контейнер std::array. Он здесь подошел бы идеально.
Не уверен, но, вроде бы, только unique_ptr умеет вызывать delete[] для массивов. shared_ptr тоже?
Не знаю.
А еще вопрос чисто по плюсам (без пользовательских библиотек, входящих в стандарт)
Что будет если:

MyClass *my = new MyClass();
delete [] my;

?
То есть удаляю как массив, создаю как одиночный.
Undefined behavior, естественно, если пользоваться стандартным аллокатором.

3.7.4.2/3:
<...> and the behavior is undefined if the value supplied to operator delete[](void*) in the standard library is not one of the values returned by a previous invocation of either operator new[](std::size_t) or operator new[](std::size_t, const std::nothrow_t&) in the standard library.
Ясно. Я ошибся, сказав «без пользовательских библиотек, входящих в стандарт» ибо оператор new имеет свою имплементацию в стандартной библиотеке(обычно обертка над malloc с вызовами конструкторов и бросанием исключений). — в принципе ничем не отличается от, скажем, vector. Правда есть еще ключевое слово new — не помню чем отличается(по моему как раз и вызывает конструктор).
А такой вопрос: а почему в стандарте по C++ описано, как должны себя вести пользовательские компоненты, стандартная library? ведь пользовательские компоненты — это не сам язык C++. Пользовательские компоненты написаны на C++ и они конечно имеют свой стандарт поведения — но не являются самим языком. По идее должно быть два источника инфы: pure C++ и C++ standart library, stl, partialy boost(что из буста перешло в C++11), etc
Ясно. Я ошибся, сказав «без пользовательских библиотек, входящих в стандарт» ибо оператор new имеет свою имплементацию в стандартной библиотеке(обычно обертка над malloc с вызовами конструкторов и бросанием исключений). — в принципе ничем не отличается от, скажем, vector. Правда есть еще ключевое слово new — не помню чем отличается(по моему как раз и вызывает конструктор).
Это ключевое слово как раз и вызывает operator new, который должен выделить память под объект, а потом вызывает необходимый конструктор.

А такой вопрос: а почему в стандарте по C++ описано, как должны себя вести пользовательские компоненты, стандартная library? ведь пользовательские компоненты — это не сам язык C++. Пользовательские компоненты написаны на C++ и они конечно имеют свой стандарт поведения — но не являются самим языком. По идее должно быть два источника инфы: pure C++ и C++ standart library, stl, partialy boost(что из буста перешло в C++11), etc
Ну, библиотека потому и стандартная, потому что входит в стандарт. Это обязательная библиотека, которая должна быть в любой реализации. Хоть разделение на ядро и библиотеку в общем-то здравое, но я не знаю таких языков, где стандартная библиотека разрабатывается совершенно отдельно от ядра языка.
я нашел про делеты и ньюшки со скобками и без. Есть две стратегии и в обоих компилятор так компилирует, что размер массива запоминается. Первый подход — с переаллоцированием для дополнительной переменной где хранится размер, второй подход — отдельный ассоциантивный массив с указателями в кач-ве ключа и размером в кач-ве значения. Эти механизмы реализуются на уровне компиляторов. www.parashift.com/c++-faq-lite/num-elems-in-new-array.html
поэтому компилятор знает сколько деструкторов вызывать.
Поэтому неопределенное поведение при использовании неправильного delete
Sign up to leave a comment.

Articles