Почему память все еще доступна после вызова std :: map :: clear ()? - PullRequest
18 голосов
/ 20 февраля 2009

Я наблюдаю странное поведение 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?

Заранее спасибо за помощь, * 1012 СНЕГ *

Ответы [ 6 ]

22 голосов
/ 20 февраля 2009

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

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

20 голосов
/ 20 февраля 2009

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

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 )

;

4 голосов
/ 20 февраля 2009

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

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

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

1 голос
/ 20 февраля 2009

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

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

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

0 голосов
/ 08 августа 2013

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

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

Это позволяет вам использовать оператор присваивания вместо 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's data

Здесь следует обратить внимание на несколько предостережений.

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

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

  5. вы можете перехватить возвращаемые значения для .insert, чтобы определить, нашел ли он существующий узел по этому ключу или создал новый. (см. код № 12). Использование индексной записи не позволяет этого. Возможно, лучше остановиться на .insert и придерживаться его, особенно потому, что нотация [] не работает с мультикартами. (это не имеет смысла, так как в мультикарте нет клавиши «a», а есть серия клавиш с одинаковыми значениями)
  6. вы можете и должны также перехватывать возвраты для .erase и .empty () (ДА, досадно, что некоторые из этих вещей являются функциями и нуждаются в (), а некоторые, как .erase, не делают)
  7. вы можете получить как значение ключа, так и значение данных для любого узла карты, используя .first и .second, которые все карты, по соглашению, используют для возврата ключа и данных соответственно
  8. сэкономьте ОГРОМНОЕ количество путаницы и печатания, и используйте 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;
    
  9. передать ссылки на карты, используя операторы typedef и &, как в

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

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

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

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

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

  12. учитывая, что «умные указатели» действительно просто ведут подсчет ссылок, помните, что вы всегда можете вести собственный подсчет ссылок, возможно, более понятным и более очевидным способом. Комбинируя это с # 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;
    }
    
  13. использование указателя для зависания на узле карты, который выделяет все для себя, т.е. IE: нет пользовательских указателей, указывающих на объекты, созданные пользователем malloc (), работает хорошо, потенциально более эффективен и может использоваться для изменения Данные узла без побочных эффектов в моем опыте.

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

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

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

0 голосов
/ 20 февраля 2009

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

__NodePtr
{
    *next;
    __Ty    Val;
}

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

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

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

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

...