std :: map, ссылки, указатели и распределение памяти - PullRequest
3 голосов
/ 29 октября 2009

Мне тяжело с картой и распределением типа стоимости.

рассмотрим этот простой класс:

class Column {
private:
    char *m_Name;
public:
    // Overrides
    const char *Name(){
        return this->m_Name;
    }

    // Ctors
    Column(const char *NewName){
        this->m_Name = new char[strlen(NewName) + 1];
        strcpy(this->m_Name, NewName);
    }

    // Dtors
    ~Column(){
        cout << "wtf?\n";
        delete this->m_Name;
    }
};

теперь у меня есть эта карта:

// Typedefs
typedef std::map<int, Column> ColumnContainer;
ColumnContainer *m_Container;

Когда я звоню это:

Column *c = new Column("Test");
cout << "CREATED: " << c->Name() << "\n";
it = this->m_Container->insert(std::make_pair(0, *c)).first;
cout << "AGAIN: " << c->Name() << "\n";

консоль печатает "wtf?" после вставки в карту.

Кажется, он уничтожает колонну. Это правильно?

или я что-то не так делаю?

Мне было интересно, имеет ли value_type из std::map структурный тип с определенным объемом памяти, как с POD или массивом POD?

cout << AGAIN не печатает «Тест»

мне нужна карта столбцов на основе ключа int

Ответы [ 6 ]

7 голосов
/ 29 октября 2009

make_pair(0, *c) создает (временный, без имени) pair<int, Column>. Эта пара передается в map::insert по ссылке, и, поскольку std::map владеет ее элементами, она создает копию пары. Затем временная пара уничтожается, уничтожая содержащийся в ней объект Column.

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

4 голосов
/ 29 октября 2009

Что радует в вашем коде:

Здесь вы динамически создаете объект. (это не освобождается в вашем коде).
Почему вы используете указатель? (Умный указатель твой друг.)
Но нормальный объект предпочтительнее.

Column *c = new Column("Test");

Теперь вы создаете копию объекта во время его копирования во временный объект типа std :: pair (при этом используется конструктор копирования Column, который создает копию члена M_name (теперь у вас есть два указателя) в ту же ячейку памяти)).

std::make_pair(0, *c)

Теперь вы вставляете пару в карту. Это снова делается с помощью конструктора копирования Column (он использует конструктор копирования make_pair, который вызывает конструктор копирования Column). Снова указатель M_name копируется в этот объект. Итак, теперь у вас есть три объекта, указывающих на динамически распределенную строку.

m_Container->insert( pairObject )

В этот момент временный объект, который вы создаете с помощью вызова std :: make_pair (), больше не нужен, поэтому он уничтожается. Это то место, где вас называют деструктором. Конечно, это оставляет вас с двумя объектами с указателем на память, которая была освобождена.

У вас большая проблема.

Вам нужно либо использовать std :: string (предпочтительное решение)
Или вам нужно правильно обработать RAW-указатель в вашем классе. Это означает, что вам нужно реализовать все четыре сгенерированных по умолчанию метода:

  • Конструктор
  • destructror
  • конструктор копирования
  • оператор присваивания

Небольшая проблема:

Вам нужно прекратить кодировать, как программисту Java.

Column *c = new Column("Test");
it = this->m_Container->insert(std::make_pair(0, *c)).first;

Должно выглядеть так:

m_Container[0] = Column("Test");

Нет необходимости динамически распределять все.
Факт, который очень опасен.

Объяснение, почему наличие указателя в RAW - плохая идея.

class X
{
    char*   m_name;
  public:
    X(char const* name)  {m_name new char[strlen(m_name) +1];strcpy(m_name,name);}
    ~X()                 {delete [] m_name;}
};

Это выглядит хорошо. Но компилятор также генерирует два метода для вас. В большинстве случаев это нормально, но не при наличии указателя в RAW.

X::X(X const& copy)
    :m_name(copy.m_name)
{}

X& X::operator=(X const& copy)
{
    m_name = copy.m_name;
}

Теперь изобразите код:

X    x("Martin");
X    y(x);

Оба x и y теперь содержат одинаковые указатели (m_name), указывающие на один и тот же фрагмент памяти. Когда 'y' выходит из области видимости, он вызывает свой derstructor, который вызывает delete [] в памяти. Теперь 'x' выходит из области видимости и вызывает delete для того же фрагмента памяти.

Z    z("Bob");
z = x;

Та же проблема, что и выше, но с использованием другого оператора.

Как это относится к вам?
Вы используете карту указателя на столбец. Карта на самом деле хранит объект Coloumn. Так что он использует конструктор Copy выше, чтобы сделать копию вашего объекта. Так что есть проблема. Но также и в коде много раз, когда временные объекты создаются и передаются.

doWork(Column const& x) { /* Do somthing intersting */

doWork(Column("Hi There"));

Здесь мы создаем временный объект Column, который передается в doWork (). Когда doWork () завершится, временная область выходит из области действия и удаляется. Но что произойдет, если doWork () сделает копию объекта, используя либо конструктор копирования, либо оператор присваивания? Это то, что происходит, когда вы вставляете объект в карту. Вы создаете временную пару, а затем копируете это значение в карту. Эта временная пара затем уничтожается.

3 голосов
/ 29 октября 2009

Основная причина, по которой ваша строка m_Name не печатается второй раз, заключается в том, как STL строит карту. Он делает различные копии значения во время его вставки. Из-за этого m_Name уничтожается в одной из копий исходного столбца.

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

2 голосов
/ 29 октября 2009

m_Name в вашем контейнере должен быть строкой, чтобы его можно было скопировать в карту.

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

попробуйте упростить, выполнив

class Column {
private:
    std::string m_Name;
public:
    // Overrides
    const char *Name(){
        return m_Name.c_str();
    }
};

Вы получаете конструктор копирования бесплатно благодаря C ++ и всем вашим членам, которые могут быть скопированы.

0 голосов
/ 29 октября 2009

То, что вы видите, это то, что временная копия Column уничтожается. Если вы используете конструктор, вы должны увидеть создаваемую копию.

0 голосов
/ 29 октября 2009

Почему бы не использовать std::string в качестве имени столбца? В самом деле ? И тогда все в порядке.

Потому что здесь у вас есть ряд проблем ... начиная с вашего деструктора (используйте delete[], когда распределение выполняется new[])

Кроме того, вам действительно нужно создать новую колонку с новым?

Давайте перепишем это:

class Column
{
public:
  Column() : m_name() {}
  Column(const std::string& name) : m_name(name) {}

  const std::string& getName() const { return m_name; }

private:
  std::string m_name;
};

А теперь ваш код вставки:

std::map<int,Column> m_container;

Column myColumn = Column("Test");
std:cout << "CREATED: " << myColumn.getName() << std::endl;
m_container[0] = myColumn; // COPY the column
std::cout << "AGAIN: " << myColumn.getName() << std::endl;

И здесь все хорошо. И еще более приятный синтаксис

m_container[0] = Column("Test");

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

...