C ++ оператор + и * неконстантная перегрузка - PullRequest
2 голосов
/ 11 марта 2010

У меня есть следующая сложная проблема: я реализовал (довольно сложный) класс, который представляет математические функции на основе нескольких волн. Поскольку в этом контексте такие операции, как +, - и * вполне естественны, я реализовал перегруженные операторы для этого класса:

FunctionTree<D> operator+(FunctionTree<D> &inpTree);
FunctionTree<D> operator-(FunctionTree<D> &inpTree);
FunctionTree<D> operator*(FunctionTree<D> &inpTree);

Там операторы работают лучше в простых, не связанных цепях операциях, и даже в некоторых случаях, когда операторы объединяются в цепочки. Заявления как

FunctionTree<3> y = a * b + c;
FunctionTree<3> z = a * b + b;

скомпилируйте и, казалось бы, работайте нормально. Первая строка на самом деле в порядке, но вторая заставляет valgrind рассказывать мрачные истории об освобождении памяти внутри уже освобожденных областей и обращении к неинициализированным переменным. Кроме того, заявление типа

FunctionTree<D> y = a + b * c;

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

Есть ли способ решить эту проблему? Единственное решение, которое у меня есть в настоящее время, состоит в том, чтобы сделать возвращаемые объекты постоянными, таким образом, эффективно отключив цепочку операторов.

С уважением, .jonas.

Ответы [ 5 ]

5 голосов
/ 11 марта 2010

Если ваши объекты имеют размер 1 ГБ (я полагаю, это память, которую они выделяют в куче, а не их фактический sizeof размер), вы, вероятно, не должны поддерживать эти операторы на них. Проблема в том, что ваши примеры цепочки операторов более или менее предполагают неизменные объекты в качестве базовой модели «того, что происходит», и создают много временных объектов, которые служат промежуточными результатами. Вы не можете рассчитывать на то, что компилятор сможет эффективно использовать пространство. Но вы также не можете копировать объекты размером 1 ГБ, волей-неволей.

Вместо этого вы должны поддерживать только различные присваивающие операторы. Тогда ваш клиент вместо того, чтобы писать:

y = a * b + c;

, который может создавать огромные временные, должен вместо этого написать:

// y = a * b + c
y = a;
y *= b;
y += c;

Таким образом, пользователь может держать вещи под контролем. Легко видеть, что временные файлы не создаются, и вы не случайно напишите простую строку кода, для запуска которой требуется 18 ГБ. Если вы хотите сделать:

y = a * b + c * d;

тогда ваш код должен явно указать, что здесь требуется временный результат (при условии, что вы не можете удалить ни одну из a, b, c, d):

// y = a * b + c * d
y = a;
y *= b;
{
   FunctionTree x = c;
   x *= d;
   y += x;
}

но если звонящий узнает, что, например, после этого c не нужен, вы можете явно сделать:

// y = a * b + c * d  [c is trashed]
c *= d;
y = a;
y *= b;
y += c;

Теоретически, компилятор может решить все эти задачи для себя, основываясь на большом выражении с цепочечными операторами и анализе потока данных, чтобы показать, что c не используется. Хорошие компиляторы с включенной оптимизацией хороши для целочисленной арифметики, так что механизм есть. На практике я бы на это не рассчитывал. Если компилятор не может доказать, что конструктор и деструктор FunctionTree не имеют видимых побочных эффектов, то его способность пропускать их ограничивается конкретными случаями юридического «исключения из копий» в стандарте.

Или вы можете посмотреть на интерфейс C к GMP, чтобы увидеть, как это делается без перегрузки оператора вообще. Все функции там принимают «параметр out», в который записывается результат. Так, например, если x - это огромное целое число с кратной точностью, которое вы хотите умножить на 10, вы выбираете:

mpz_mul_ui(x, x, 10); // modifies x in place, uses less memory

или

mpz_t y;
mpz_init(y);
mpz_mul_ui(y, x, 10); // leaves x alone, result occupies as much memory again.
2 голосов
/ 11 марта 2010

Простого решения этой проблемы не существует. Ваши двоичные операторы (правильно) создают безымянные временные объекты - такие объекты не могут быть связаны с неконстантными ссылочными параметрами.

Один из возможных способов обойти это - отказаться от цепочки операторов - для класса X:

X a;
X b;
X c = a * b;
X d;
X e  = c + d;

Другое (довольно ужасное) решение состоит в том, чтобы сделать элементы данных класса изменяемыми - таким образом они будут логически постоянными, но физически изменяемыми. Затем вы должны сделать ваши параметры постоянными ссылками. Как я уже сказал, это ужасно и может вызвать другие проблемы.

1 голос
/ 11 марта 2010

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

Кроме того, вы можете сделать большинство членов приватными.

#include <memory>
#include <iostream>

struct FunctionTreeProxy;
struct FunctionTree;

struct FunctionTreeProxy {
    mutable std::auto_ptr<FunctionTree> ft;

    explicit FunctionTreeProxy(FunctionTree * _ft): ft(_ft) {}
    FunctionTreeProxy(FunctionTreeProxy const & rhs): ft(rhs.ft) {}

    FunctionTreeProxy operator+(FunctionTree & rhs);
    FunctionTreeProxy operator*(FunctionTree & rhs);
    FunctionTreeProxy operator+(FunctionTreeProxy const & rhs);
    FunctionTreeProxy operator*(FunctionTreeProxy const & rhs);
};

struct FunctionTree {
    int i;
    FunctionTree(int _i): i(_i) {}
    FunctionTree(FunctionTreeProxy const & rhs): i(rhs.ft->i) {}

    FunctionTree * add(FunctionTree & rhs) {
        return new FunctionTree(i + rhs.i);
    }

    FunctionTree * mult(FunctionTree & rhs) {
        return new FunctionTree(i * rhs.i);
    }

    FunctionTreeProxy operator+(FunctionTree & rhs) {
        return FunctionTreeProxy(add(rhs));
    }

    FunctionTreeProxy operator*(FunctionTree & rhs) {
        return FunctionTreeProxy(mult(rhs));
    }

    FunctionTreeProxy operator+(FunctionTreeProxy const & rhs) {
        return FunctionTreeProxy(add(*rhs.ft));
    }

    FunctionTreeProxy operator*(FunctionTreeProxy const & rhs) {
        return FunctionTreeProxy(mult(*rhs.ft));
    }
};

FunctionTreeProxy FunctionTreeProxy::operator+(FunctionTree & rhs) {
    return FunctionTreeProxy(ft.get()->add(rhs));
}

FunctionTreeProxy FunctionTreeProxy::operator*(FunctionTree & rhs) {
    return FunctionTreeProxy(ft.get()->mult(rhs));
}

FunctionTreeProxy FunctionTreeProxy::operator+(FunctionTreeProxy const & rhs) {
    return FunctionTreeProxy(ft.get()->add(*rhs.ft));
}

FunctionTreeProxy FunctionTreeProxy::operator*(FunctionTreeProxy const & rhs) {
    return FunctionTreeProxy(ft.get()->mult(*rhs.ft));
}

int main(int argc, char* argv[])
{
    FunctionTree a(1), b(2), c(3);

    FunctionTree z = a + b * c;

    std::cout << z.i << std::endl;

    return 0;
}
1 голос
/ 11 марта 2010

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

0 голосов
/ 11 марта 2010

Передать аргументы по значению, а не по ссылке?

РЕДАКТИРОВАТЬ: вы можете использовать вместо + = - = * =.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...