【问题标题】:std::map, references, pointers and memory allocationstd::map、引用、指针和内存分配
【发布时间】:2009-10-29 17:57:11
【问题描述】:

我在 map 和 valuetype 分配方面遇到了困难。

考虑这个简单的类:

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?”在地图中插入之后。

它似乎正在破坏该列。是这样吗?

还是我做错了什么?

我想知道std::mapvalue_type 是否必须是具有定义内存大小的结构类型,例如 POD 或 POD 数组?

cout &lt;&lt; AGAIN 不打印“测试”

我需要的是基于 int 键的列映射

【问题讨论】:

  • 您不需要使用 this 指针来引用成员数据,除非您想清除同名事物的任何歧义。 “this->”已经被暗示了。例如,您可以说:“删除 m_Name;”
  • 是的,我就是喜欢用这个关键字

标签: c++ memory reference map pointers


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    你的代码发生了什么:

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

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

    现在你复制对象,因为它被复制到 std::pair 类型的临时对象中(这使用了 Column 的复制构造函数,它复制了 M_name 成员(你现在有两个指针到相同的内存位置))。

    std::make_pair(0, *c)
    

    现在您将 对插入到地图中。这是通过再次使用 Column 复制构造函数来完成的(它使用调用 Column 复制构造函数的 make_pair 复制构造函数)。再次将 M_name 指针复制到该对象中。所以现在你有三个对象指向动态分配的字符串。

    m_Container->insert( pairObject )
    

    此时,您通过调用 std::make_pair() 创建的临时对象不再需要,因此它被销毁。这是你的析构函数被调用的地方。当然,这会给你留下两个对象,它们的内存指针现在已经被释放了。

    你有一个大问题。

    您要么需要使用 std::string(首选解决方案)
    或者您需要正确处理类中的 RAW 拥有的指针。这意味着您需要实现所有四个默认生成的方法:

    • 构造函数
    • 析构函数
    • 复制构造函数
    • 赋值运算符

    小问题:

    你需要停止像 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' 现在都指向同一块内存的 contan 指针 (m_name)。当 'y' 超出范围时,它会调用它的析构函数,该析构函数调用内存上的 delete []。现在 'x' 超出范围并在同一块内存上调用 delete。

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

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

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

    doWork(Column const& x) { /* Do somthing intersting */
    
    doWork(Column("Hi There"));
    

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

    【讨论】:

    • m_Container 是一个指针,我不能在上面使用[],或者使用(*m_Container)[],对吧?为什么我不能在列中使用 char *?你能解释一下吗?
    【解决方案3】:

    您的字符串 m_Name 第二次未打印的根本原因是 STL 构建地图的方式。它在插入过程中对值进行各种复制。因此,m_Name 在原始列的副本之一中被销毁。

    另一个建议是当对象是映射中的值时使用指向对象的指针。否则,如果对象足够大,您可能会对性能造成重大影响。

    【讨论】:

    • 是的,我不会使用拥有我的变量的 stl 东西,这太复杂了......它创建了我的列对象的 3 个副本并销毁了 3 个。打算使用指针。 ..我喜欢指针。也许是因为我是新手,但我更愿意知道我的代码中究竟发生了什么,以及我在哪里创建、复制和销毁对象。
    • 从技术上讲,它可以制作三个。但是有很多编译器优化通常允许编译器就地构建对象。使用指针是危险的,并且经常会导致释放错误。如果你必须有一个指针容器,请使用 boost 指针容器,因为它们会在指针被销毁时释放指针。
    【解决方案4】:

    容器中的 m_Name 应该是一个字符串,所以它可以被复制构造成一个地图。

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

    尝试通过做来简化

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

    由于 C++ 和您的所有成员都是可复制构造的,您可以免费获得复制构造函数。

    【讨论】:

    • 你能帮我详细说明一下吗?
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

      因为这里你有很多问题......从你的析构函数开始(当分配由new[] 完成时使用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++ 已经需要大量的代码,让我们尽可能使用它提供的快捷方式。

      【讨论】:

      • 但是 myColumn 不是在堆栈上创建的吗?
      • 是的,但是因为当你把它放到地图上时你会复制它,这有关系吗?您创建它,调整它,将它复制到地图中,然后丢弃它。这里的 myColumn 在这方面只是一个“临时”对象,真正的(和有趣的)对象是它存储在地图中的副本。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-29
      • 1970-01-01
      • 1970-01-01
      • 2021-06-22
      • 2015-04-21
      相关资源
      最近更新 更多