Подтвердить что ты не робот

Почему память все еще доступна после вызова std:: map:: clear()?

Я наблюдаю странное поведение std:: map:: clear(). Этот метод должен вызывать деструктор элемента при вызове, однако память по-прежнему доступна после вызова clear().

Например:

struct A
{
  ~A() { x = 0; }
  int x;
};

int main( void )
{
  std::map< int, A * > my_map;
  A *a = new A();
  a->x = 5;
  my_map.insert( std::make_pair< int, *A >( 0, a ) );

  // addresses will be the same, will print 5
  std::cout << a << " " << my_map[0] << " " << my_map[0]->x << std::endl;

  my_map.clear();

  // will be 0
  std::cout << a->x << std::endl;

  return 0;
}

Вопрос в том, почему переменная a все еще доступна после того, как ее деструктор был вызван map:: clear()? Нужно ли писать delete a; после вызова my_map.clear() или безопасно перезаписать содержимое a?

Заранее благодарим за помощь, снег

4b9b3361

Ответ 1

std:: map не управляет памятью, на которую указывают значения указателя - это вам, чтобы сделать это самостоятельно. Если вы не хотите использовать интеллектуальные указатели, вы можете написать бесплатную и понятную функцию общего назначения, например:

template <typename M> void FreeClear( M & amap ) 
    for ( typename M::iterator it = amap.begin(); it != amap.end(); ++it ) {
        delete it->second;
    }
    amap.clear();
}

И используйте его:

std::map< int, A * > my_map;
// populate
FreeClear( my_map )

;

Ответ 2

Если вы сохраняете указатели на карте (или списке или что-то в этом роде) YOU отвечают за удаление указателей, так как карта не знает, были ли они созданы с новыми, или нет. Функция clear только вызывает деструкторы, если вы не используете указатели.

О, и еще одна вещь: вызов деструктора (или даже вызов delete) не означает, что доступ к памяти больше невозможен. Это означает, что вы будете получать доступ к мусору, если вы это сделаете.

Ответ 3

Это потому, что map.clear() вызывает деструкторы данных, содержащихся на карте, в вашем случае, указателя на a. И это ничего не делает.

Возможно, вы захотите поместить какой-то умный указатель на карте, чтобы память, занятая a, была автоматически исправлена.

Кстати, почему вы вставляете аргументы шаблона в вызов make_pair? Вычисление аргумента шаблона должно здесь очень хорошо.

Ответ 4

Когда вы освобождаете кучу памяти кучи, его содержимое не обнуляется. Они просто доступны для распределения снова. Разумеется, вы должны считать память не доступной, потому что эффекты доступа к нераспределенной памяти undefined.

Фактически предотвращение доступа к странице памяти происходит на более низком уровне, а библиотеки std этого не делают.

Когда вы выделяете память новым, вам нужно удалить ее самостоятельно, если вы не используете интеллектуальный указатель.

Ответ 5

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

Есть также некоторые тонкие преимущества, например, если вы копируете данные из файла или сокета в данные карты, хранилище данных существует, как только существует node, потому что когда карта вызывает malloc(), чтобы выделить node, он выделяет память как для ключа, так и для данных. (Карта AKA [ключ]. Первая и карта [ключ]. Секунд)

Это позволяет использовать оператор присваивания вместо memcpy() и требует 1 меньше вызова для malloc() - того, который вы делаете.

IC_CDR CDR, *pThisCDRLeafData;  // a large struct{}

    while(1 == fread(CDR, sizeof(CDR), 1, fp))  {
    if(feof(fp)) {
        printf("\nfread() failure in %s at line %i", __FILE__, __LINE__);
    }
    cdrMap[CDR.iGUID] = CDR; // no need for a malloc() and memcpy() here    
    pThisCDRLeafData  = &cdrMap[CDR.iGUID]; // pointer to tree node data

Несколько предупреждений, о которых следует помнить, заслуживают упоминания здесь.

  • НЕ вызывать malloc() или new в строке кода, которая добавляет дерево node, так как ваш вызов malloc() вернет указатель ПЕРЕД вызовом карты в malloc() выделил место для хранения возврата от вашего malloc().
  • в режиме отладки, ожидайте, что у вас возникнут аналогичные проблемы при попытке освободить() вашу память. Оба они кажутся мне проблемой компилятора, но по крайней мере в MSVC 2012 они существуют и являются серьезной проблемой.
  • подумайте о том, где "привязать" ваши карты. IE: где они объявлены. Вы не хотите, чтобы они выходили из сферы действия по ошибке. main {} всегда безопасен.

    INT _tmain(INT argc, char* argv[])    {
    IC_CDR      CDR, *pThisCDRLeafData=NULL;
    CDR_MAP     cdrMap;
    CUST_MAP    custMap;
    KCI_MAP     kciMap;
    
  • Мне очень повезло, и мне очень приятно, что критическая карта выделяет структуру как данные node, и эта структура "привязывает" карту. В то время как анонимные структуры были оставлены С++ (ужасное, ужасное решение, которое ДОЛЖНО быть отменено), карты, которые являются 1-й структурой, работают так же, как анонимные структуры. Очень гладкий и чистый с нулевыми размерами. Передача указателя на структуру, принадлежащую листу, или копию структуры по значению в вызове функции, работают очень хорошо. Очень рекомендуется.

  • вы можете уловить возвращаемые значения для .insert, чтобы определить, нашел ли он существующий node на этом ключе или создал новый. (см. № 12 для кода) Использование нотации индекса не позволяет этого. Возможно, было бы лучше обосноваться и встать с ним, особенно потому, что [] нотация не работает с мультиплексами. (было бы бессмысленно делать это, так как не существует ключа "a", а серии ключей с одинаковыми значениями в мультимаре)
  • вы можете и должны также возвращать ловушки для .erase и .empty() (ДА, это раздражает, что некоторые из этих вещей являются функциями, и им нужны() и некоторые, например .erase, не)
  • вы можете получить как значение ключа, так и значение данных для любой карты node с использованием .first и .second, которые все сопоставляют, по соглашению, для возврата ключа и данных соответственно
  • сохраните ОГРОМНОЕ количество путаницы и набора текста и используйте typedef для ваших карт, например.

    typedef map<ULLNG, IC_CDR>      CDR_MAP;    
    typedef map<ULLNG, pIC_CDR>     CALL_MAP;   
    typedef struct {
        CALL_MAP    callMap;
        ULNG        Knt;         
        DBL         BurnRateSec; 
        DBL         DeciCents;   
        ULLNG       tThen;       
        DBL         OldKCIKey;   
    } CUST_SUM, *pCUST_SUM;
    typedef map<ULNG,CUST_SUM>  CUST_MAP, CUST_MAP;  
    typedef map<DBL,pCUST_SUM>  KCI_MAP;
    
  • передавать ссылки на карты с помощью typedef и и оператора, как в

    ULNG DestroyCustomer_callMap(CUST_SUM Summary, CDR_MAP& cdrMap, KCI_MAP& kciMap)

  • используйте тип переменной "auto" для итераторов. Компилятор будет вычислять из типа, указанного в остальной части тела цикла for(), какой тип typedef карты использовать. Он настолько чист, что почти волшебный!

    for(auto itr = Summary.callMap.begin(); itr!= Summary.callMap.end(); ++itr) {

  • определите некоторые константы манифеста, чтобы сделать возврат из .erase и .empty() более значимым.

    if(ERASE_SUCCESSFUL == cdrMap.erase (itr->second->iGUID)) {

  • учитывая, что "умные указатели" на самом деле просто поддерживают подсчет ссылок, помните, что вы всегда можете сохранить свой собственный счетчик ссылок, возможно, в чистом и более очевидном виде. Объединив это С# 5 и # 10 выше, вы можете написать хороший чистый код, подобный этому.

    #define Pear(x,y) std::make_pair(x,y) //  some macro magic
    
    auto res = pSumStruct->callMap.insert(Pear(pCDR->iGUID,pCDR));
    if ( ! res.second ) {
        pCDR->RefKnt=2;
    } else {
        pCDR->RefKnt=1;
        pSumStruct->Knt += 1;
    }
    
  • используя указатель, чтобы вставить на карту node, которая выделяет все для себя, IE: никакие указатели пользователя, указывающие на пользовательские объекты malloc(), работают хорошо, потенциально более эффективны и могут использоваться для мутировать данные node без побочных эффектов в моем опыте.

  • по той же теме, такой указатель можно использовать очень эффективно, чтобы сохранить состояние node, как в pThisCDRLeafData выше. Передавая это функции, которая мутирует/изменяет, что конкретные данные node чище, чем передача ссылки на карту, и указывает ключ, необходимый для возврата к node pThisCDRLeafData.

  • итераторы не являются волшебными. Они дороги и медленны, поскольку вы перемещаете карту, чтобы получить значения. Для карты, содержащей миллион значений, вы можете прочитать node на основе ключа со скоростью около 20 миллионов в секунду. С итераторами он, вероятно, ~ 1000 раз медленнее.

Я думаю, что об этом сейчас накрывает. Будет обновляться, если какое-либо из этих изменений изменится или появится дополнительная информация. Мне особенно нравится использовать STL с кодом C. IE: не класс в поле зрения. Они просто не имеют смысла в контексте, в котором я работаю, и это не проблема. Удачи.

Ответ 6

Любой контейнер хранит ваш объект. Введите и вызовите соответствующие конструкторы: внутренний код, каждый node может выглядеть примерно так:

__NodePtr
{
    *next;
    __Ty    Val;
}

Когда вы его выделяете, это происходит путем построения значения val на основе типа и последующего связывания. Нечто похожее на:

_Ty _Val = _Ty();
_Myhead = _Buynode();
_Construct_n(_Count, _Val);

При удалении он вызывает соответствующие деструкторы.

Когда вы храните ссылки (указатели), он не будет вызывать никакого конструктора и не разрушит.