views:

416

answers:

7

I am having a lil hard time with map and the valuetype allocation.

consider this simple class:

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;
    }
};

now I have this map:

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

When i call this:

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";

the console is printing the "wtf?" after the insert in the map.

it seems to be destroying the column. Is this right?

or am I doing something wrong?

I was wondering if the value_type of the std::map has to a struct type with defined memory size, like with POD or array of POD?

the cout << AGAIN doesn't print the "Test"

what I need is a map to a columns based on int key

+1  A: 

Why not using a std::string for the Column name ? Really ? And then everything's alright.

Because here you've got a number of problems... beginning with your destructor (use delete[] when allocation is done by new[])

Also, do you really need to create your column with new ?

Let's rewrite that:

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;
};

And now your insertion code:

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;

And here everything is fine. And even slickier syntax

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

C++ already requires a fair amount of code bloat, let's use the shortcuts it offers whenever possible.

Matthieu M.
but isn't myColumn created on the stack?
Jonathan
Yes it is, but since you will make a COPY of it when you put it in the map, does it matter ? You create it, tweak it, copy it into the map, and then discard it. Here myColumn is just a 'temporary' object in this regard, the real (and interesting) object is its copy stored in the map.
Matthieu M.
+2  A: 

The m_Name in your container should be a string, so it can be copy-constructed into a map.

Right now you are not defining a proper copy constructor, so it's only copying m_Name as a pointer, which is invalid.

try simplifying by doing

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

You get the copy constructor for free thanks to C++ and all your members being copy constructible.

Snazzer
+1. Also make Name() a const method. ;)
David Joyner
could you elaborate it better for me please?
Jonathan
A: 

What you're seeing is that a temporary copy of Column is being destroyed. If you instrument the constructor you should see the copy being created.

David Joyner
+6  A: 

make_pair(0, *c) creates a (temporary, unnamed) pair<int, Column>. This pair is passed to map::insert by reference, and, since std::map owns its elements, it makes a copy of the pair. Then, the temporary pair is destroyed, destroying the Column object it contains.

This is why it is necessary to properly define copy-construction for types that you want to use as standard container elements.

Éric Malenfant
+2  A: 

The underlying reason why your string m_Name doesn't print the second time is because of the way the STL builds a map. It makes various copies of the value during its insertion. Because of this, m_Name gets destroyed in one of the copies of the original column.

Another piece of advice is to use pointers to objects when the object is a value in the map. Otherwise you could be taking a major performance hit of the object is large enough.

kidnamedlox
yeah, I am not going to use stl stuff with them owning my variables, it is too complicated... it created like 3 copies of my column object and destroyed the 3. Going to use pointers... I like pointers. Maybe because I am new at this but I prefer to know what is exatcly happening in my code and where I am creating, copying and destroying objects.
Jonathan
Technically it can make three. But there are a lot of compiler optimisations that often allow the compiler to build the object in place. Using pointers is dangerious and will often lead to mistakes in deallocation. If you must have a container of pointers use the boost pointer containers as they will deallocate the pointer when it is destroyed.
Martin York
+2  A: 

What is happining in your code:

Here you are dynamically creating an object. (this is not freed in your code).
Why are you using a pointer? (Smart pointer are your friend.)
But a normal object would be preferable.

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

Now you make a copy of the object as it is copied into the temporary object of type std::pair<int,Column> (This uses Column's copy constructor which makes a copy of the M_name member (you now have two pointers to the same memory location)).

std::make_pair(0, *c)

Now you insert the pair<int,Column> into the map. This is done by again using the Column copy constructor (it uses the make_pair copy constructor which calls the Column copy constructor). Again the M_name pointer is copied into this object. So now you have three objects poining at the dynamically allocated string.

m_Container->insert( pairObject )

At this point the temporary object you create with the call to std::make_pair() is no longer needed so it is destroyed. This is where you destructor is getting called. Of course this leaves you with two objects with pointer at memory that has now been released.

You have a big problem.

You either need to use std::string (preferred solution)
Or you need to correctly handle the RAW owned pointer in your class. This means you need to implement all four of the defaultly generated methods:

  • constructor
  • destructror
  • copy constructor
  • assignment operator

Small problem:

You need to stop coding like a java programmer.

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

Should look like this:

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

There is no need to dynamically allocate everything.
Infact that is very dangerious.

Explanation for why having a RAW owned pointer is a bad idea.

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;}
};

This looks fine. But the compiler is also generating two methods for you. In mnost situations this is fine but not when you have a RAW owned pointer.

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

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

Now image the code:

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

Both 'x' and 'y' now contan pointers (m_name) pointing at the same piece of memory. When 'y' goes out of scope it calls its derstructor which calls the delete [] on the memory. Now 'x' goes out of scope and calls delete on the same piece of memory.

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

Same problem as above jsut using a different operator.

How does this apply to you?
You are using a map of pointer to Column. The map actually stores a Coloumn object. So it is using the Copy constructor above to make a copy of your object. So there is a probelm. But also in code there is a lot of times when temporary objects are created and passed around.

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

doWork(Column("Hi There"));

Here we create a temporary Column object that is passed to the doWork(). When doWork() is complete the temporay goes out of scope and is deleted. But what happens if doWork() makes a copy of the object using either the copy costructor or assignment operator? This is what is happening when you insert the object into the map. You are creating a temporary pair then copying this value into the map. This temporarty pair is then being destroyed.

Martin York
m_Container is a pointer, I can't use [] on it, or use (*m_Container)[], right? and why can't I use char * within column? could you explain a little more about this?
Jonathan
My point is it should probably not be a pointer.
Martin York
A: 

Why people don't answer comments?

there shouldn't be commends but only replies because people just don't answer it.

:(

Jonathan