2D матрица и оператор перегрузки () / уродливый синтаксис - PullRequest
4 голосов
/ 25 марта 2009

Я использую 2D-матрицу в одном из моих проектов. Это похоже на C ++ FAQ Lite .

Удобно то, что вы можете использовать его так:

int main()
{
  Matrix m(10,10);
  m(5,8) = 106.15;
  std::cout << m(5,8);
  ...
}

Теперь у меня есть граф, состоящий из вершин, и каждая вершина имеет открытый (просто для простоты примера) указатель на 2D-матрицу, как указано выше. Теперь у меня есть довольно уродливый синтаксис для доступа к нему.

(*sampleVertex.some2DTable)(0,0) = 0; //bad
sampleVertex.some2DTable->operator()(0,0) = 0; //even worse...

Возможно, мне здесь не хватает синтаксического сахара из-за моей неопытности с перегрузкой операторов. Есть ли лучшее решение?

Ответы [ 9 ]

4 голосов
/ 25 марта 2009

Если у вас есть указатель на матрицу, например, в качестве параметра функции, который вы не можете сделать ссылку (устаревший код, например), вы все равно можете сделать ссылку на него (псевдокод):

struct Matrix {
        void operator () (int u, int v) {
        }
};
int main () {
        Matrix *m;
        Matrix &r = *m;
        r (1,1);
}
4 голосов
/ 25 марта 2009
  1. Рассмотрите возможность использования ссылок вместо указателей (при условии, что оно не может быть нулевым, и вы можете инициализироваться в конструкторе).
  2. Рассмотрим создание метода получения или экземпляра класса-оболочки матрицы для вершины, которая возвращает ссылку на 2D-матрицу (при условии, что она не может быть нулевой).

    sampleVertex.some2DTable()(0,0) = 0;
    sampleVertex.some2DTableWrap(0,0) = 0;
    

Однако для меня это звучит как не проблема, чтобы оправдать преодоление всех неприятностей.

2 голосов
/ 25 марта 2009

Вы в основном ограничены (*sampleVertex.some2DTable)(0,0). Конечно, если вам не нужно пересматривать, почему бы не сохранить вместо этого фактические значения в матрице?

В качестве альтернативы, сделайте указатель приватным и сделайте метод доступа (примечание: в следующих примерах предполагается матрица EntryTypes):

Matrix& Vertex::GetTableRef() 
{
    return *some2DTable; 
}
// or
Matrix::EntryType& Vertex::GetTableEntry(int row, int col)
{
    return (*some2DTable)(row,col);
}

// way later...
myVertex.GetTableRef()(0,0) = 0;
// or...
myVertex.GetTableEntry(0,0) = 0;

Или просто определите встроенную функцию, чтобы сделать это для вас, если вы не можете изменить класс Vertex:

// in some header file
inline Matrix& GetTableRef(Vertex& v)
{
    return *v.some2DTable;
}

// or you could do this
inline Matrix::EntryType& GetTableEntry(Vertex& v, int row, int col)
{
    return (*v.some2DTable)(row, col);
}


// later...
GetTableRef(myVertex)(0, 0) = 0;
// or
GetTableEntry(myVertex, 0, 0) = 0;

Наконец, не забывайте, что у вас нет для использования перегрузки операторов. Коллекции STL реализуют функцию-член at (), которая проверяется, в отличие от оператора [], который не проверяется. Если вы не возражаете против накладных расходов, связанных с проверкой границ, или если вы просто хотите быть нестандартными, вы можете реализовать at (), а затем просто вызвать myVertex.some2DTable->at(0,0), что полностью избавит вас от синтаксической головной боли.

1 голос
/ 25 марта 2009

Я бы добавил функцию, которая возвращает вам ссылку, которую рекомендует rlbond. Для быстрого исправления или если у вас нет контроля над его источником, я бы сказал следующее:

sampleVertex.some2DTable[0](0,0) = 0; // more readable

Это на самом деле эквивалентно, потому что следующее, если a является указателем на определенный класс:

*a == *(a + 0) == a[0]

См. это длинное обсуждение на comp.lang.c ++ об этой же проблеме с хорошими ответами.

1 голос
/ 25 марта 2009

Не существует синтаксического сахара C ++, который облегчил бы боль того, что вы описываете:

(*sampleVertex.some2DTable)(0,0) = 0; //bad
sampleVertex.some2DTable->operator()(0,0) = 0; //even worse...

В этой ситуации я бы либо имел график, возвращающий ссылку вместо указателя, либо чтобы матрица определяла функцию, которая вызывает operator ():

inline matrixType &Matrix::get( int x, int y ){ return operator()(x,y); }

Тогда синтаксис не такой уродливый для примера вершины:

sampleVertex.some2DTable->get(0,0) = 0;
0 голосов
/ 26 марта 2009

Вы можете реализовать Matrix :: operator (int, int), вызвав функцию-член и использовать ее непосредственно при работе с указателями.

class Matrix
{
public:
  float ElementAt( int i, int j ) const { /*implement me*/ }
  float operator() ( int i, int j ) const { return ElementAt( i, j ); }
  ...
};

void Foo(const Matix* const p)
{
  float value = p->ElementAt( i, j );
  ...
}

void Bar(const Matrix& m)
{
  float value = m(i,j);
}
0 голосов
/ 25 марта 2009

Я бы изменил способ получения «sampleVertex.some2DTable», поэтому он возвращает ссылку.

Либо так, либо создайте ссылку самостоятельно:

Matrix& m = *sampleVertex.some2DTable;
m(1,2) = 3;
0 голосов
/ 25 марта 2009

Я не знаю, стоит ли эта проблема, но вы могли бы сделать:

class MatrixAccessor {
private:
  Matrix2D* m_Matrix;
public:
  MatrixAccessor(Matrix2D* matrix) : m_matrix(matrix) { }
  double& operator()(int i, int j) const { return (*m_Matrix)(i,j); }
  Matrix2D* operator->() const { return m_Matrix; }
  void operator=(Matrix2D* matrix) { m_Matrix = matrix; }
};

При условии, что оригинал operator() возвращает ссылку (как во многих классах матрицы).

Затем вы предоставляете MatrixAccessor в вашем классе вершин:

class Vertex {
  Matrix2D* myMatrix;

public:
  MatrixAccessor matrix;
  Vertex(Matrix2D *theMatrix) : myMatrix(theMatrix), matrix(theMatrix) { }
};

Тогда вы можете написать:

Vertex v;
v.matrix(1,0) = 13;
v.matrix->SomeOtherMatrixOperation();

EDIT

Я добавил const ключевые слова (спасибо @ phresnel за поднятие темы), чтобы сделать решение семантически эквивалентным решению, представляющему только публичный Matrix2D -показатель.

Преимущество этого решения состоит в том, что константность может быть передана объекту матрицы путем добавления двух не const версий operator()() и operator->() (то есть матрица не может быть изменена в вершинах const) и изменив const единиц, чтобы вернуть const double& и const Matrix2D* соответственно.

Это было бы невозможно при использовании публичного указателя на объект матрицы.

0 голосов
/ 25 марта 2009

Это лучший способ без изменения вашего кода:

//some2DTable is a pointer to a matrix
(*sampleVertex.some2DTable)(0,0)

Вы также можете вместо этого сделать some2DTable ссылкой на матрицу вместо указателя на матрицу. Тогда вы бы упростили синтаксис, как в вашем первом фрагменте кода.

//some2DTable is a reference to a matrix instead of a pointer to a matrix
sampleVertex.some2DTable(0,0)

Или вы можете оставить some2DTable указателем на ссылку и просто сохранить переменную ссылки на нее и использовать ее в контексте вашего кода.

...