std :: map,引用,指针和内存分配

时间:2009-10-29 17:57:11

标签: c++ memory reference map pointers

我在使用地图和值类型分配时遇到了困难。

考虑这个简单的类:

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的{​​{1}}是否必须具有已定义内存大小的结构类型,例如POD还是POD数组?

std::map 不打印“测试”

我需要的是基于int键

的列映射

6 个答案:

答案 0 :(得分:7)

make_pair(0, *c)创建一个(临时的,未命名的)pair<int, Column>。该对通过引用传递给map::insert,并且,由于std::map拥有其元素,因此它会复制该对。然后,销毁临时对,销毁它包含的Column对象。

这就是为什么有必要为要用作标准容器元素的类型正确定义复制构造。

答案 1 :(得分:4)

您的代码中有什么好处:

在这里,您将动态创建一个对象。 (这不会在您的代码中释放。) 你为什么用指针? (智能指针是你的朋友。)
但是一个普通的物体会更好。

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

现在,您可以复制对象,因为它被复制到std :: pair&lt; int,Column&gt;类型的临时对象中。 (这使用Column的复制构造函数,它复制了M_name成员(现在有两个指向同一内存位置的指针)。)

std::make_pair(0, *c)

现在插入对&lt; int,Column&gt;进入地图。这是通过再次使用Column copy构造函数完成的(它使用调用Column复制构造函数的make_pair复制构造函数)。 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;}
};

看起来很好。但编译器也为您生成两种方法。在mnost情况下,这很好,但是当你有一个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;

与上面使用不同运算符的jsut相同的问题。

这对您有何影响?
您正在使用指向Column的指针映射。地图实际上存储了一个Coloumn对象。所以它使用上面的Copy构造函数来制作对象的副本。所以有一个问题。但是在代码中,很多时候创建和传递临时对象。

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

doWork(Column("Hi There"));

这里我们创建一个传递给doWork()的临时Column对象。当doWork()完成时,临时超出范围并被删除。但是,如果doWork()使用复制结构函数或赋值运算符复制对象,会发生什么?这是将对象插入地图时发生的情况。您正在创建一个临时对,然后将此值复制到地图中。然后这个临时对被摧毁。

答案 2 :(得分:3)

字符串m_Name第二次不打印的根本原因是STL构建映射的方式。它在插入过程中会生成各种值的副本。因此,m_Name在原始列的一个副本中被销毁。

另一条建议是当对象是地图中的值时使用指向对象的指针。否则你可能会对该对象产生重大的性能影响。

答案 3 :(得分:2)

容器中的m_Name应该是一个字符串,因此可以将其复制到地图中。

现在你没有定义一个合适的拷贝构造函数,所以它只是将m_Name复制为指针,这是无效的。

尝试通过

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

由于C ++而且你的所有成员都是可复制的,你可以免费获得拷贝构造函数。

答案 4 :(得分:0)

为什么不使用std::string作为列名?真的吗?然后一切都好。

因为在这里你遇到了许多问题......从你的析构函数开始(delete[]完成分配时使用new[]

另外,您真的需要使用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 ++已经需要相当多的代码膨胀,让我们尽可能使用它提供的快捷方式。

答案 5 :(得分:0)

您所看到的是正在销毁Column的临时副本。如果您检测构造函数,则应该看到正在创建的副本。