C : о последующих операциях перегруженного оператора

#c #class #operator-overloading

#c #класс #оператор-перегрузка

Вопрос:

 #include <iostream>
#include <vector>
using namespace std;


void testfn(double* v1, double *v2, double *v3, int n);//I must use this function

class CLS{
private:
    vector<double> v;
public:
    CLS(vector<double> vin);
    CLS operator (CLS amp; A);
    CLS operator*(CLS amp; A);
};

CLS::CLS(vector<double> vin)
{
    v=vin;
}

CLS CLS::operator*(CLS amp;A){
    //assuming the two vectors have the same length
    vector<double> vtmp(v.size(),0);
    testfn(amp;*A.v.begin(),amp;*v.begin(),amp;*vtmp.begin(),(int)A.v.size());
    CLS C(vtmp);
    return C;
}

CLS CLS::operator (CLS amp;A){
    //assuming the two vectors have the same length
    vector<double> vtmp(v.size(),0);
    testfn(amp;*A.v.begin(),amp;*v.begin(),amp;*vtmp.begin(),(int)A.v.size());
    CLS C(vtmp);
    return C;
}

void testfn(double* v1, double *v2, double *v3, int n)
{
    for (int i=0;i<n;i  )
    {
        *v3=*v1 *v2;
          v1;
          v2;
          v3;
    }
}

int main(){
    vector<double> v1(100,1.0), v2(100,2.0), v3(100,0.0);
    CLS C1(v1),C2(v2),C3(v3);
    CLS C4=C1*(C1 (C2*C3 C2))*C1*C2*C3 C1 C2 C3;
    return 0;
}
  

Я создаю CLS класса и определил два оператора и *. Я хочу использовать эти операторы так же просто, как мы используем и * для целых и двойных чисел. Поэтому у меня есть тестовая строка в основном CLS C4=C1*(C1 (C2*C3 C2))*C1*C2*C3 C1 C2 C3; . Однако я получаю тонны ошибок при компиляции этого кода с использованием gcc. Я недостаточно знаком с правилами перегрузки оператора. Как я должен изменить определение (возможно, просто параметры?) * и , чтобы CLS C4=C1*(C1 (C2*C3 C2))*C1*C2*C3 C1 C2 C3; это было допустимо?

Дополнения: Я не знаю, как использовать параметр const для операторов и * , поскольку определение этих двух операторов включает в себя другую функцию testfn , параметры которой являются double* вместо const double* . Более того, я не хочу изменять какую-либо часть в определении testfn , потому что в моем реальном коде это соответствует функции в LAPACK, которую я абсолютно не имею права изменять.

Комментарии:

1. «Я недостаточно знаком с правилами перегрузки оператора». И так ли сложно найти в Google «оператор c плюс перегрузка»?

2. В строке «научите человека ловить рыбу» вот ссылка на «перегрузку двоичной операции» . Не стесняйтесь прочитать всю страницу (для всех операторов).

3. @AdrianColomitchi В исходном сообщении я добавил некоторое объяснение о том, почему я не могу использовать параметры const для и *. Вполне возможно, что эти трудности можно обойти. Но я не знаю как.

4. Если вы точно знаете, что это testfn не приведет к изменению *v1 or *v2 , тогда вы можете безопасно отбросить const эти аргументы при вызове testfn .

5. @Resorter C 11 вы говорите? Может быть, вам пора опереться на move copy и move assign , а не полагаться на сгенерированные компилятором? Смотрите также правило 5/3/0 . Актуальность: вы возвращаете объекты по значению, должны понимать, какие механизмы лежат в основе,

Ответ №1:

Лучше определить operator and operator* как дружественную функцию, чтобы, если будут неявные преобразования из какого-либо другого типа в CLS , тогда оператор можно было использовать. Если это функция-член, то, если первый операнд не является CLS , он не будет найден.

 friend CLS operator (CLS lhs, const CLSamp; rhs) 
{
  // Do your logic here
  return lhs; 
}
  

Комментарии:

1. Спасибо за это предложение. Я буду иметь это в виду. Это действительно помогает мне организовать код лучшим образом.

2. Привет, Никита, теперь я хочу расширить определение матричного умножения на вектор * матрица, матрицу * вектор, вектор * вектор (внутреннее произведение) и вектор * вектор (внешнее произведение). Как я должен объявить функцию, чтобы она работала во всех пяти случаях? Спасибо.

Ответ №2:

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

Это не обязательно, но небольшая оптимизация, передайте аргумент конструктора по ссылке, а затем инициализируйте v элемент в списке инициализации конструктора (не присваивая ему в теле):

 CLS::CLS(const vector<double>amp; vin) : v(vin)
{
}
  

Я бы добавил новый конструктор, который принимает желаемый размер и немедленно приводит v элемент к этому размеру, что упрощает приведенный ниже код:

 CLS::CLS(int n) : v(n, 0)
{
}
  

Сначала добавьте const в параметр функции и саму функцию-член:

 CLS CLS::operator*(const CLSamp; a) const {
                   ^^^^^         ^^^^^
  

Затем по умолчанию создается пустой C объект для результата с векторным элементом желаемого размера:

     //assuming the two vectors have the same length
    CLS result(v.size());
  

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

     vector<double> v1 = a.v;
    vector<double> v2 = v;
  

Теперь вы можете получать неконстантные указатели на данные в этих векторах, и вы можете записывать выходные данные прямо в C.v вместо того, чтобы записывать в vtmp , а затем копировать это в возвращаемый объект:

     testfn(amp;*v1.begin(), amp;*v2.begin(), amp;*result.v.begin(), v.size());
  

Наконец, верните результат:

     return resu<
}
  

И точно такие же изменения для другой функции:

 CLS CLS::operator (const CLSamp; a) const {
    //assuming the two vectors have the same length
    CLS result(v.size());
    vector<double> v1 = a.v;
    vector<double> v2 = v;
    testfn(amp;*v1.begin(), amp;*v2.begin(), amp;*result.v.begin(), v.size());
    return resu<
}
  

Теперь он сможет принимать аргументы const, поэтому должен работать с временными объектами, которые создаются каждым выражением C1 C2 или C3*C4 .

Код немного менее эффективен из-за дополнительных копий данных, но оптимизация в конструкторе помогает этому. Даже эти недостатки могут быть решены, если вы можете использовать C 11, но для этого требуется больше работы.

Во-первых, C 11 предоставляет vector<T>::data() возможность получения указателя, что намного понятнее, чем amp;*v.begin() . (Это не было частью стандарта C до C 11, но GCC предоставляет это даже для C 98). Вы можете изменить приведенный выше код, чтобы использовать v.data() вместо amp;*v.begin() , как показано ниже.

В C 11 вы можете добавить дополнительные перегрузки, которые работают с временными объектами, путем привязки (неконстантных) ссылок rvalue на временные объекты. Поскольку ссылки на значения rvalue предоставляют вам неконстантный доступ к временным объектам, вам не нужно делать копии их данных, чтобы получить неконстантные указатели, и вы можете использовать их напрямую. Добавьте эти перегрузки оператора в дополнение к исходным, показанным выше:

 CLS CLS::operator (CLSamp;amp; a) amp;amp; {
    //assuming the two vectors have the same length
    CLS result(v.size());
    testfn(a.v.data(), v.data(), result.v.data(), v.size());
    return resu<
}

CLS CLS::operator*(CLSamp;amp; a) amp;amp; {
    //assuming the two vectors have the same length
    CLS result(v.size());
    testfn(a.v.data(), v.data(), result.v.data(), v.size());
    return resu<
}
  

Это позволяет избежать создания каких-либо копий внутри промежуточных временных объектов, созданных вашими арифметическими выражениями. Я также считаю, что это намного проще для понимания и легче для чтения.

Полный рабочий пример находится на http://coliru.stacked-crooked.com/a/6e40d20eb7f1e9fc

Комментарии:

1. Ваше предложение по оптимизации имеет абсолютный смысл. Создание копий CLS известно, но, как вы сказали, это менее эффективно. Я действительно использую C 11, так что можете ли вы сказать мне, как полностью решить эту проблему? Спасибо.

2. Я исправил вашу опечатку при определении результата. Но почему он все еще не компилируется на моем собственном компьютере? Я объявляю как оператор CLS (CLS amp;amp; a) amp;amp;; в разделе public. Затем я просто скопировал ваш код и исправил опечатку, но он по-прежнему не компилировался.

3. Я исправил еще несколько опечаток и добавил ссылку на полный рабочий пример. Убедитесь, что вы добавили новые перегруженные операторы в определение класса и добавили const к исходным объявлениям в определении класса.

4. Фантастика. Большое спасибо!

5. Извините, но можете ли вы избежать копирования всего CLS в и *?

Ответ №3:

Необычное требование использовать testfn со всеми неконстантными параметрами немного странно, но это … мм.. неохотно приемлемый неприемлемо для случая сложных выражений — первые два параметра testfn должны быть const double* .

Обоснование: стандарт C требует привязки к временным файлам const references — вот некоторые интересные подробности

@Resorter говорит:

«Спасибо, что дали мне знать, и я хотел бы научиться. Но вы уверены, что это решает мою проблему? Я действительно хочу сначала решить эту проблему.»

[где это ‘переместить конструктор’ и ‘переместить назначение’]


Следующая «рыба» предполагает, что вы прочитали / изучили поиск конструктора перемещения и назначение перемещения и правила 5/3/0,

Дополнительный учебный материал: исключение копирования — когда компилятору разрешено пропускать конструкторы копирования / перемещения, даже если они могут иметь побочные эффекты.

 // some implementations
void testfn0(const double* v1, const double *v2, double *v3, int n) {
  for(int i=0; i<n; i  ) {
    v3[i]=v1[i] v2[i];
  }
}

void testfn1(double* v1, double *v2, double *v3, int n) {
  for(int i=0; i<n; i  ) {
    v3[i]=v1[i]*v2[i];
  }
}

class CLS {
  std::vector<double> v;
public:
  // constructor taking a const reference
  CLS(const std::vector<double>amp; in) : v(in) {
    std::cout << "copy vec" << std::endl;
  }

  // extra constructor with rvalue reference: it will "canibalize" the parameter
  CLS(std::vector<double>amp;amp; in) : v(in) {
    std::cout << "move vec" << std::endl;
  }

  // Rule of 5 applied
  // Copy constructor
  CLS(const CLSamp; other) : v(other.v) {
    std::cout << "copy CLS" << std::endl;
  }
  // Move constructor
  CLS(CLSamp;amp; other) : v(std::move(other.v)) {
    std::cout << "move CLS" << std::endl;
  }
  ~CLS() { }

  // Copy assgn
  CLSamp; operator=(const CLSamp; o) {
    this->v=o.v;
    std::cout << "assgn CLS" << std::endl;
    return *this;
  }
  // Move assgn
  CLSamp; operator=(CLSamp;amp; other) {
    this->v=std::move(other.v);
    std::cout << "move CLS" << std::endl;
    return *this;
  }

  // WILL NOT WORK WITHOUT const FOR COMPLEX EXPRESSIONS
  //                       |
  //              ----------
  //              V
  CLS operator (const CLSamp; rhs) {
    std::vector<double> vtmp(v.size(),0);
    testfn0(amp;*rhs.v.begin(),amp;*v.begin(),amp;*vtmp.begin(),(int)rhs.v.size());

    CLS ret(std::move(vtmp));
    return ret;
  }

  // WILL NOT WORK WITHOUT const FOR COMPLEX EXPRESSIONS
  //                       |
  //              ----------
  //              V
  CLS operator*(const CLSamp; rhs) {
    std::vector<double> vtmp(v.size(),0);
    testfn1(amp;*rhs.v.begin(),amp;*v.begin(),amp;*vtmp.begin(),(int)rhs.v.size());

    CLS ret(std::move(vtmp));
    return ret;
  }
};

int main() {
  std::cout << "n--- inits " << std::endl;
  CLS a(std::vector<double>{1,2,3}); // move vec
  CLS b(std::vector<double>{3,2,1}); // move vec

  std::cout << "n--- add " << std::endl;
  CLS c=a b; // move vec and 'silence' (copy elision)
  std::cout << "n--- mul " << std::endl;
  CLS d=a*b; // move vec and 'silence' (copy elision)

  std::cout << "n--- copy " << std::endl;
  CLS m=c; // Copy CLS - constructor

  std::cout << "n--- move " << std::endl;
  CLS n=std::move(d); // Move CLS - constructor

  std::cout << "n--- assgn (d=c) and copy (x=c)" << std::endl;
  CLS x=d=c;
}
  

Комментарии:

1. @Resorter Ах, (на всякий случай) не забудьте скомпилировать с -std=c 11 или лучше

2. Босс, в вашем коде нет проблем. Я могу скомпилировать и запустить его. Но я хочу, чтобы и * использовались таким образом, как x = a b * c d. Я добавил эту строку, после чего снова не удалось выполнить компиляцию.

3. @Resorter вы правы. Вы уверены, что testfn должна иметь подпись all-no-const вместо void testfn(const double* v1, const double *v2, double *v3, int n) ? Почему первые 2 параметра не могут быть const ? Что именно должна делать эта функция (эти функции)?

4. Я должен использовать эту (и некоторые другие) функцию LAPACK в моем реальном коде

5. внешний «C» { аннулирует DGEMM ( char * TRANSA, char * TRANSB, int *M, int *N, int *K, double *ALPHA, double *A, int * LDA, double *B, int * LDB, double * BETA, double * C, int * LDC);