簡體   English   中英

c ++模板類的運算符

[英]c++ template class's operators

我通過創建自己的數據結構類(確切地說是矩陣)來教自己c ++,並且我只使用雙精度將其更改為<T>類型的模板類。 重載矩陣運算符非常標准

    // A snippet of code from when this matrix wasn't a template class
    // Assignment
    Matrix& operator=( const Matrix& other );

    // Compound assignment
    Matrix& operator+=( const Matrix& other ); // matrix addition
    Matrix& operator-=( const Matrix& other ); // matrix subtracton
    Matrix& operator&=( const Matrix& other ); // elem by elem product
    Matrix& operator*=( const Matrix& other ); // matrix product

    // Binary, defined in terms of compound
    Matrix& operator+( const Matrix& other ) const; // matrix addition
    Matrix& operator-( const Matrix& other ) const; // matrix subtracton
    Matrix& operator&( const Matrix& other ) const; // elem by elem product
    Matrix& operator*( const Matrix& other ) const; // matrix product

    // examples of += and +, others similar
    Matrix& Matrix::operator+=( const Matrix& rhs )
    {
        for( unsigned int i = 0; i < getCols()*getRows(); i++ )
        {
            this->elements.at(i) += rhs.elements.at(i);
        }
        return *this;
    }

    Matrix& Matrix::operator+( const Matrix& rhs ) const
    {
        return Matrix(*this) += rhs;
    }

但是現在Matrix可以有一個類型,我無法確定哪個矩陣引用應該是<T>類型以及后果是什么。 我應該允許不同類型相互操作(例如,Matrix <foo> a + Matrix <bar> b是否有效)? 我對如何有點模糊

我對不同類型感興趣的一個原因是為了便於將來使用復數。 我是c ++的新手,但很高興能夠深入學習。 如果您熟悉任何處理此問題的免費在線資源,我會發現最有幫助的。

編輯:難怪沒有人認為這是有道理的我身體的所有尖括號被視為標簽! 我無法弄清楚如何逃避它們,所以我將內聯代碼。

我認為我應該說明我對參數化矩陣維度的評論,因為您之前可能沒有看過這種技術。

template<class T, size_t NRows, size_t NCols>
class Matrix
{public:
    Matrix() {} // `data` gets its default constructor, which for simple types
                // like `float` means uninitialized, just like C.
    Matrix(const T& initialValue)
    {   // extra braces omitted for brevity.
        for(size_t i = 0; i < NRows; ++i)
            for(size_t j = 0; j < NCols; ++j)
                data[i][j] = initialValue;
    }
    template<class U>
    Matrix(const Matrix<U, NRows, NCols>& original)
    {
        for(size_t i = 0; i < NRows; ++i)
            for(size_t j = 0; j < NCols; ++j)
                data[i][j] = T(original.data[i][j]);
    }

private:
    T data[NRows][NCols];

public:
    // Matrix copy -- ONLY valid if dimensions match, else compile error.
    template<class U>
    const Matrix<T, NRows, NCols>& (const Matrix<U, NRows, NCols>& original)
    {
        for(size_t i = 0; i < NRows; ++i)
            for(size_t j = 0; j < NCols; ++j)
                data[i][j] = T(original.data[i][j]);
        return *this;
    }

    // Feel the magic: Matrix multiply only compiles if all dimensions
    // are correct.
    template<class U, size_t NOutCols>
    Matrix<T, NRows, NOutCols> Matrix::operator*(
        const Matrix<T, NCols, NOutCols>& rhs ) const
    {
        Matrix<T, NRows, NOutCols> result;
        for(size_t i = 0; i < NRows; ++i)
            for(size_t j = 0; j < NOutCols; ++j)
            {
                T x = data[i][0] * T(original.data[0][j]);
                for(size_t k = 1; k < NCols; ++k)
                    x += data[i][k] * T(original.data[k][j]);
                result[i][j] = x;
            }
        return result;
    }

};

所以你要聲明一個2x4 float矩陣,初始化為1.0,如下:

Matrix<float, 2, 4> testArray(1.0);

請注意,由於大小是固定的,因此不需要將存儲放在堆上(即使用operator new )。 你可以在堆棧上分配它。

你可以創建另外幾個int的矩陣:

Matrix<int, 2, 4> testArrayIntA(2);
Matrix<int, 4, 2> testArrayIntB(100);

對於復制,尺寸必須匹配,但類型不符合:

Matrix<float, 2, 4> testArray2(testArrayIntA); // works
Matrix<float, 2, 4> testArray3(testArrayIntB); // compile error
// No implementation for mismatched dimensions.

testArray = testArrayIntA; // works
testArray = testArrayIntB; // compile error, same reason

乘法必須具有正確的尺寸:

Matrix<float, 2, 2> testArrayMult(testArray * testArrayIntB); // works
Matrix<float, 4, 4> testArrayMult2(testArray * testArrayIntB); // compile error
Matrix<float, 4, 4> testArrayMult2(testArrayIntB * testArray); // works

請注意,如果存在botch, 則會在編譯時捕獲它。 但是,只有在矩陣維度在編譯時固定時才可以這樣做。 另請注意,此邊界檢查不會導致其他運行時代碼。 如果您只是使尺寸保持不變,那么它就是您所獲得的相同代碼。

調整

如果您在編譯時不知道矩陣維度,但必須等到運行時,此代碼可能沒什么用處。 您必須編寫一個內部存儲維度的類和一個指向實際數據的指針,並且它需要在運行時執行所有操作。 提示:編寫operator []將矩陣視為重新整形的1xN或Nx1向量,並使用operator ()執行多索引訪問。 這是因為operator []只能接受一個參數,但operator ()沒有這樣的限制。 通過嘗試支持M[x][y]語法,很容易在腳中射擊(迫使優化器放棄,至少)。

也就是說,如果有一種標准矩陣大小調整,你可以將一個Matrix調整為另一個, 因為所有維度在編譯時都是已知的 ,那么你可以編寫一個函數來調整大小。 例如,此模板函數會將任何Matrix重新Matrix為列向量:

template<class T, size_t NRows, size_t NCols>
Matrix<T, NRows * NCols, 1> column_vector(const Matrix<T, NRows, NCols>& original)
{   Matrix<T, NRows * NCols, 1> result;

    for(size_t i = 0; i < NRows; ++i)
        for(size_t j = 0; j < NCols; ++j)
            result.data[i * NCols + j][0] = original.data[i][j];

    // Or use the following if you want to be sure things are really optimized.
    /*for(size_t i = 0; i < NRows * NCols; ++i)
        static_cast<T*>(result.data)[i] = static_cast<T*>(original.data)[i];
    */
    // (It could be reinterpret_cast instead of static_cast. I haven't tested
    // this. Note that the optimizer may be smart enough to generate the same
    // code for both versions. Test yours to be sure; if they generate the
    // same code, prefer the more legible earlier version.)

    return result;
}

......好吧,我認為這是一個列向量,無論如何。 希望很明顯如何修復它。 無論如何,優化器將看到您返回result並刪除額外的復制操作,基本上將結果構建在調用者想要查看的位置。

編譯時維度完整性檢查

假設我們希望編譯器在維度為0停止(通常導致空Matrix )。 我聽說過一個名為“Compile-Time Assertion”的技巧,它使用模板特化並聲明為:

template<bool Test> struct compiler_assert;
template<> struct compiler_assert<true> {};

這樣做是為了讓你編寫如下代碼:

private:
    static const compiler_assert<(NRows > 0)> test_row_count;
    static const compiler_assert<(NCols > 0)> test_col_count;

基本思想是,如果條件為true ,模板將變為空struct ,無人使用並被靜默丟棄。 但是如果它是false ,編譯器找不到struct compiler_assert<false>定義 (只是一個聲明 ,這是不夠的)和錯誤。

更好的是Andrei Alexandrescu的版本(來自他的書 ),它允許您使用聲明的斷言對象名稱作為即興錯誤消息:

template<bool> struct CompileTimeChecker
{ CompileTimeChecker(...); };
template<> struct CompileTimeChecker<false> {};
#define STATIC_CHECK(expr, msg) { class ERROR_##msg {}; \
    (void)sizeof(CompileTimeChecker<(expr)>(ERROR_##msg())); }

你填寫的msg必須是一個有效的標識符(僅限字母,數字和下划線),但這沒什么大不了的。 然后我們只需用以下內容替換默認構造函數:

Matrix()
{   // `data` gets its default constructor, which for simple types
    // like `float` means uninitialized, just like C.
    STATIC_CHECK(NRows > 0, NRows_Is_Zero);
    STATIC_CHECK(NCols > 0, NCols_Is_Zero);
}

瞧,如果我們錯誤地將其中一個維度設置為0 ,編譯器就會停止。 有關它的工作原理,請參閱Andrei的書第25頁。 請注意,在true情況下,只要測試沒有副作用,生成的代碼就會被丟棄,因此沒有膨脹。

我不確定我明白你在問什么。

但我要指出您的運營商聲明不正確和/或不完整。

首先,賦值運算符應返回與其參數相同的類型; 即:

const Matrix&operator =(const Matrix&src);

其次,二元運算符返回一個新對象 ,因此您無法返回引用。 所有二元運算符都應該聲明:

Matrix operator+( const Matrix& other ) const; // matrix addition
Matrix operator-( const Matrix& other ) const; // matrix subtracton
Matrix operator&( const Matrix& other ) const; // elem by elem product
Matrix operator*( const Matrix& other ) const; // matrix product

實際上,將二元運算符聲明和實現為全局友元函數被認為是更好的樣式:

class Matrix { ... };

inline Matrix operator+(const Matrix& lhs,const Matrix& rhs)
{ return Matrix(lhs)+=rhs; }

希望這可以幫助。


現在我明白你在問什么了。

據推測,在這種情況下,您對各種運算符的實現將包含對復合類型的操作。 那么Matrix op Matrix是否有意義的問題取決於字符串op int是否有意義(以及這樣的事情是否有用)。 您還需要確定返回類型可能是什么。

假設返回類型與LHS操作數相同,聲明將類似於:

template <typename T>
class Matrix
{
    template <typename U>
    Matrix<T>&  operator+=(const Matrix<U>& rhs);
};

template <typename T,typename U>
Matrix<T> operator+(const Matrix<T>& lhs,const Matrix<U>& rhs)
{ return Matrix<T>(lhs)+=rhs; }
Matrix<double> x = ...;
Matrix<int> y = ...;
cout << x + y << endl; // prints a Matrix<double>?

好的,這是可行的,但問題很快就會變得棘手。

Matrix<double> x = ...
Matrix<complex<float>> y = ...
cout << x + y << endl; // Matrix<complex<double>>?

如果您要求二進制運算符使用類似操作數並強制應用程序構建器顯式地鍵入其值,那么您很可能會感到最開心。 對於后一種情況:

cout << ((Matrix<complex<double>>) x) + ((Matrix<complex<double>>) y) << endl;

您可以提供成員模板構造函數(或類型轉換運算符)來支持轉換。

template <typename T>
class Matrix {
   ...
public:
   template <typename U>
   Matrix(const Matrix<U>& that) { 
       // initialize this by performing U->T conversions for each element in that
   }
   ...
};

另一種方法是,讓你的二元運算符模板根據兩個操作數的元素類型推導出正確的Matrix返回類型,需要一些中等復雜的模板元編程,可能不是你想要進入的。

首先,復制賦值運算符不應該有const Matrix&作為其返回類型; 你的界面是正確的。

格蘭特關於如何實現二元運算符的建議是普遍接受的做這些事情的方式。

這是一個很好的練習,但很快就會發現為什么在C ++中進行線性代數是一個壞主意。 僅當矩陣的維數匹配時, A+BA*B才有效。

您根本不需要添加太多內容,因為在模板中,類名本身引用了當前模板參數。 所以以下是等價的:

template <typename T> struct Foo
{
  Foo<T> bar(const Foo<T> &);
  Foo bar2(const Foo *);       // same
};

所以你的所有操作都是經過不加改變的。 你應該添加的是一個將一種矩陣類型轉換為另一種矩陣類型的構造函數:

temlate <typename T> class Matrix
{
  template <typename U> Matrix(const Matrix<U> &);  // construct from another matrix
  /*...*/
};

使用該轉換構造函數,您可以在運算符中混合矩陣,因為Matrix<T>::operator+(Matrix<U>)將使用轉換創建Matrix<T>類型的參數,然后使用已經實現的運算符。

在C ++ 11中,您可以添加static_assert(std::is_convertible<U, T>::value, "Boo"); 如果您使用不兼容的類型調用它,那么轉換構造函數將為您提供有用的編譯時診斷。

暫無
暫無

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

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