2011-07-07 11 views
2

我想爲多維數組設計一個C++類。通過多維我的意思是1d,2d,3d等。該類支持按元素加法和標量乘法運算符。假設A和B是類的實例(尺寸相同的尺寸爲&)。我想使用的對象在表達這樣的:在C++中設計一個多維數組

C = A * 2 + B

我想知道的事情是如何管理內存。在上面的表達式中,A * 2將創建該類的臨時對象,稍後將其添加到B.反正,臨時對象在添加完成後是垃圾。我寫了下面的類,它很好地工作,但我很確定它泄漏了內存。現在我的問題是,

  1. 我該如何解決內存問題?什麼是設計課程的最佳方式?
  2. 是否可以在堆棧而不是堆上分配所需的內存?

    class XArray 
    { 
        int num_dim; 
        int *dims; 
        int *index_helper; 
        int table_size; 
        double *table; 
    
    
    public: 
        XArray(const int n, const int *d):num_dim(n), dims(d) 
        { 
         int size = 1; 
         for (int i = 0; i < n; i++) { 
          size *= d[i]; 
         } 
         table_size = size; 
         table = new double[size]; 
         index_helper = new int[n]; 
        }; 
    
        ~XArray() 
        { 
         delete[] table; 
         delete[] index_helper; 
        }; 
    
        int dim(int d) 
        { 
         return dims[d]; 
        } 
    
        double& operator()(int i) 
        { 
         index_helper[0] = i; 
         return get_helper(1, index_helper); 
        } 
    
        double& operator()(int i, int j) 
        { 
         index_helper[0] = i; 
         index_helper[1] = j; 
         return get_helper(2, index_helper); 
        } 
    
        double& operator()(int i, int j, int k) 
        { 
         index_helper[0] = i; 
         index_helper[1] = j; 
         index_helper[2] = k; 
         return get_helper(3, index_helper); 
        } 
    
        XArray operator*(double m) 
        { 
         XArray *xa = new XArray(num_dim, dims); 
         for (int i = 0; i < table_size; i++) { 
          xa->table[i] = this->table[i] * m; 
         } 
    
         return *xa; 
        } 
    
        XArray operator+(const XArray &that) 
        { 
         if (num_dim != that.num_dim) { 
          char *msg = new char[100]; 
          sprintf(msg, "XArray::dimensions do not match in + operation, expected %d, found %d", num_dim, that.num_dim); 
          throw msg; 
         } 
    
         for (int i = 0; i < num_dim; i++) { 
          if (this->dims[i] != that.dims[i]) { 
           char *msg = new char[100]; 
           sprintf(msg, "XArray::dimension %d not mached, %d != %d", i, dims[i], that.dims[i]); 
           throw msg; 
          } 
         }  
    
         XArray *xa = new XArray(num_dim, dims); 
         for (int i = 0; i < table_size; i++) { 
          xa->table[i] = this->table[i] + that.table[i]; 
         } 
    
         return *xa; 
        } 
    
    private: 
        double& get_helper(int n, int *indices) 
        { 
         if (n != num_dim) { 
          char *msg = new char[100]; 
          sprintf(msg, "XArray::dimensions do not match, expected %d, found %d", num_dim, n); 
          throw msg; 
         } 
    
         int multiplier = 1; 
         int index = 0; 
    
         for (int i = 0; i < n; i++) { 
          if (indices[i] < 0 || indices[i] >= dims[i]) { 
           char *msg = new char[100]; 
           sprintf(msg, "XArray::index %d out of range, %d not in (0, %d)", i, indices[i], dims[i]); 
           throw msg; 
          } 
    
          index += indices[i] * multiplier; 
          multiplier *= dims[i]; 
         } 
    
        return table[index]; 
    } 
    

    };

+0

事實證明,數組的維數真的屬於類型系統本身。幸運的是,C++有模板。您可以使'XArray <1>'爲一維陣列,'XArray <2>'二維等。 – MSalters

+2

如何使用現有的[多維數組類](http://www.boost.org/doc/libs/1_46_1/libs/multi_array/doc/index.html)? –

+0

@Kerrek SB - Boost.Multi_array不支持Mohsen似乎期望的元素添加和標量乘法操作。儘管如此,它可能對底層實現很有用。 – Mankarse

回答

1

這個問題應該可能在Code Review而不是在這裏。但在設計上有一些東西:

手動管理內存通常不是一個好主意,您應該更喜歡使用現有的容器而不是動態分配的數組。構造函數獲取傳入指針的所有權並不常見,並可能導致用戶代碼出現問題。目前您禁止某些用途,如:

int dims[3] = { 3, 4, 5 }; 
XArray array(3, dims); // or use sizeof(dims)/sizeof(dims[0]), or other trickery 

這是比簡單的用戶代碼:

int ndims = 5; 
int *dims = new int[ndims] 
XArray array(ndims, dims); 

這也意味着用戶必須意識到,dims所有權已經被收購,他們不能delete指針。

在內部,我會使用std::vector來維護動態內存,因爲這會免費爲您提供正確的語義。因爲您需要實現複製構造函數和賦值運算符(或禁用它們),因爲當前代碼不會泄漏,但可能會嘗試將某些內存加倍釋放。請記住三者的規則:如果您提供拷貝構造函數,賦值運算符析構函數中的任何一個,那麼您應該提供全部三種。

operator*operator+泄漏內存(XArray對象本身的內部存儲器實際上是由於沒有拷貝構造函數的處理,但不認爲這是一個理由不創造拷貝構造函數,對相反你必須創建它)

投擲char*是允許的,但我會建議你不要因爲不同的原因。異常的類型應提供有關發生的情況的信息,否則,您的用戶代碼將不得不解析異常的內容以確定發生了什麼問題。即使您決定使用單一類型,請注意,在重載解析中使用的相同規則不適用於異常處理,尤其是您可能在轉換未發生時遇到問題。這也是設計中存在內存泄漏的另一個潛在可能,如拋出一個指向動態分配內存的指針,如果用戶不釋放內存,或者如果用戶沒有真正將內存釋放給用戶,則釋放內存的責任關心異常並抓住一切(catch (...) {})你會泄漏錯誤。

如果在你的程序中可能有這種情況,那麼它可能會更好,如果這是可能的話(這是一個設計決定:錯誤的尺寸有多糟糕,即使例外情況也會發生,或者應該沒有發生?)。

index_helper不屬於該類的成員,它是由不同operator()get_helper()方法只共享一個實現細節,則應該從類中刪除它,並且可以在每個operator()靜態分配它。這也有點奇怪,你提供單獨的operator() 1,2和3維度,而代碼是通用的,以處理任何維度,並且您還實際上要求用戶知道表示出公共接口,其中兩項作業被禁止,這也不是一個很好的設計。

+0

非常感謝你的完美答案。我傾斜了很多。其實,我不是CPP程序員。你提到的問題的一部分,如私人的index_helper,喜歡斷言的例外,來自我在java文化背景。我嘲笑由C++編寫的java代碼很多,因爲他們試圖像cpp一樣編寫java代碼。我敢肯定,當你看我的代碼時,你有同樣的感覺:)只是一個簡單的問題,有沒有什麼辦法在C++中編寫類型安全可變參數函數?當我編寫通用代碼時,我不喜歡放鬆類型安全。 – Helium

+0

通用代碼通常是通過模板處理的(這是我錯過Java中最多的功能之一),完全是類型安全的。與Java泛型類型相比,泛型類型被類型擦除,在C++模板中爲每個實例化生成不同的類/函數。如果你在你的代碼中引用'operator()',那是目前要做的最複雜的事情之一。如果你有一個支持可變參數模板的C++編譯器,那麼它就簡單得多了,但是目前這很難做到(即問題是參數個數不固定)。 –

+0

如果你想創建這種類型的通用N維數組,我會考慮使用N作爲模板參數,它會爲每個實例創建不同的類,然後你可以使用預處理器生成N參數'operator()'這並不好,並且很難使其可讀,但它可以完成。雖然創建一個通用的包含元素類型的數組非常簡單,但創建一個通用維數的數組並且使用乾淨的接口這樣做並不是一件簡單的任務。 –

4

我想念複製構造函數和賦值操作符。你將需要這些來創建內部緩衝區的副本,而不僅僅是複製指針,否則你最終將釋放相同的內存兩次。

如果您要支持C++ 0x,您還可能需要實現移動語義以獲得最佳性能。

像你一樣投擲動態分配的對象是一個非常糟糕的主意。

在您的運營商*您在堆上創建對象:

XArray operator*(double m) 
{ 
    XArray xa(num_dim, dims); 
    for (int i = 0; i < table_size; i++) { 
     xa->table[i] = this->table[i] * m; 
    } 

    return xa; 
} 

但是隻要你不寫你(移動)拷貝構造函數,這將崩潰。因爲return語句會創建一個指向與xa相同的內部數據的副本。 xa將被銷燬,並且它的析構函數將銷燬它的內部數據。所以正在返回的臨時對象在內部指向已銷燬的數據。

編輯:

鏈接信息關於Move semantics or rvalue references

+0

有沒有辦法在堆棧上分配內部字段(如表格)?實際上,從XArray創建的對象不是很大,但我將使用數百萬個實例,就像數百萬次評估一些複雜的表達式一樣。所以,我認爲如果我可以在堆棧中分配它們會更有效率。 – Helium

+0

+1,很棒的鏈接! – juanchopanza

+0

@Mohsen,只有在編譯時已知大小的情況下才可以在堆棧上進行分配。 – Eelke

2

我建議以下,以避免內存泄漏:

  1. 拷貝構造函數operator =()private和未執行,以避免tableindex_helper被覆蓋(並導致內存泄漏意外)。如果你希望你使用它們,那麼確保,你做delete[] tabledelete[] index_helper;之前重新分配,以避免泄漏。
  2. 對於錯誤消息,使用std::string 而不是char *msg = new char[100];。它更清潔,並且可維護。
  3. 請勿使用XArray *xa = new XArray(num_dim, dims);;而不是 只是聲明它爲XArray xa;。 這將是在棧和自動清盤 (如果你的C++ 0x的支持,那麼你可以選擇完美轉發,以消除不必要的副本)
與問候

現在你剩下class設計,答案非常主觀,需要詳細的代碼分析。所以,我會留給你決定。

+0

關於第3部分的問題:如果我使用「XArray xa」該對象在堆棧中構建,但內部表在堆上。對?是否通過調用析構函數自動刪除內部存儲的內存(如字段表)? – Helium

+1

@Mohsen,是'XArray'在棧中,當它超出範圍時,調用它的析構函數'〜XArray()',在那裏刪除堆內存。所以你不必擔心那部分。 – iammilind

+0

我對你的第一個建議有點困惑。如果我使拷貝構造函數是私有的,我怎麼能有一個像A = B這樣的任務?如果我編寫自己的拷貝構造函數並複製內部字段會發生什麼? – Helium