簡體   English   中英

std :: map,引用,指針和內存分配

[英]std::map, references, pointers and memory allocation

我正忙着使用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 << AGAIN不打印“測試”

我需要的是基於int鍵的列映射

make_pair(0, *c)創建一個(臨時的,未命名的) pair<int, Column> 這對通過引用傳遞給map::insert ,並且由於std::map擁有它的元素,因此它會復制該對。 然后,銷毀臨時對,銷毀它包含的Column對象。

這就是為什么有必要為要用作標准容器元素的類型正確定義復制構造的原因。

您的代碼中有什么好處:

在這里,您將動態創建一個對象。 (這不會在您的代碼中釋放)。
你為什么用指針? (智能指針是你的朋友。)
但是一個普通的物體會更好。

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

現在,您將對象的副本復制到std :: pair <int,Column>類型的臨時對象中(這使用了Column的復制構造函數,該構造函數生成了M_name成員的副本(現在您有兩個指向該對象的指針)記憶位置))。

std::make_pair(0, *c)

現在將對<int,Column>插入到地圖中。 這是通過再次使用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()使用復制結構函數或賦值運算符復制對象,會發生什么? 這是將對象插入地圖時發生的情況。 您正在創建一個臨時對,然后將此值復制到地圖中。 然后這個臨時對被摧毀。

字符串m_Name第二次不打印的根本原因是STL構建映射的方式。 它在插入過程中會生成各種值的副本。 因此,m_Name在原始列的一個副本中被銷毀。

另一條建議是當對象是地圖中的值時使用指向對象的指針。 否則你可能會對該對象的主要性能影響足夠大。

容器中的m_Name應該是一個字符串,因此可以將其復制到地圖中。

現在你沒有定義一個合適的拷貝構造函數,所以它只是將m_Name復制為指針,這是無效的。

嘗試通過做簡化

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

由於C ++,您可以免費獲得復制構造函數,並且所有成員都可以復制構造。

為什么不使用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 ++已經需要相當多的代碼膨脹,讓我們盡可能使用它提供的快捷方式。

您所看到的是正在銷毀Column的臨時副本。 如果您檢測構造函數,則應該看到正在創建的副本。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM