#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);