Как сделать переменную всегда равной результату некоторых вычислений? - PullRequest
57 голосов
/ 28 марта 2019

В математике, если z = x+y/2, то z всегда будет меняться всякий раз, когда мы заменяем значения x и y. Можем ли мы сделать это в программировании без необходимости конкретного обновления z всякий раз, когда мы меняем значения x и y?

Я имею в виду что-то подобное не будет работать, верно?

int x;
int y;
int z{x + y};
cin >> x;
cin >> y;
cout << z;

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

Как и при убийстве крипа и получении золота, показанная чистая стоимость (деньги + стоимость собственных предметов) меняется. Или измеритель скорости автомобиля, который меняется в зависимости от скорости или скорости движения.

Ответы [ 12 ]

57 голосов
/ 28 марта 2019

Редактировать: Пока я полностью отвечал на вопрос в ответ на вопрос, пожалуйста, посмотрите также Артелиус ' ответ . В нем рассматриваются некоторые вопросы, на которые мой ответ не распространяется (инкапсуляция, избежание избыточности, риски висячих ссылок). Возможная оптимизация, если расчет стоит дорого, показана в Jonathan Mee 's answer .


Вы имеете в виду что-то вроде этого:

class Z
{
    int& x;
    int& y;
public:
    Z(int& x, int& y) : x(x), y(y) { }
    operator int() { return x + y; }
};

Класс задерживает вычисление результата, пока он не будет приведен как int. Поскольку оператор приведения не является явным, Z может использоваться всякий раз, когда требуется int. Поскольку для int есть перегрузка operator<<, вы можете использовать его с e. г. std::cout напрямую:

int x, y;
Z z(x, y);
std::cin >> x >> y;
if(std::cin) // otherwise, IO error! (e. g. bad user input)
    std::cout << z << std::endl;

Имейте в виду, однако, что все еще является вызовом функции (неявным оператором приведения), даже если он невидим. И на самом деле оператор выполняет некоторые истинные вычисления (а не просто получает доступ к внутреннему члену), поэтому сомнительно, что хорошая идея - скрыть вызов функции ...

49 голосов
/ 28 марта 2019

Вы можете приблизиться к этому, используя лямбду в C ++.Обычно, когда вы устанавливаете переменную, такую ​​как

int x;
int y;
int z{x + y};

z будет только результатом x + y в то время.Вам придется делать z = x + y; каждый раз, когда вы изменяете x или y, чтобы поддерживать его обновление.

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

int x;
int y;
auto z = [&](){ return x + y; };
cin >> x;
cin >> y;
cout << z();

, и теперь z() будет иметь правильное значение вместо неинициализированного мусора, который имел оригинальный код.

Если вычисления очень дороги, вы можете даже добавить кеширование в лямбду, чтобы убедиться, что вычисления не выполняются, когда вам это не нужно.Это было бы похоже на

auto z = [&](){ static auto cache_x = x; 
                static auto cache_y = y; 
                static auto cache_result = x + y;
                if (x != cache_x || y != cache_y)
                {
                    cache_x = x; 
                    cache_y = y; 
                    cache_result = x + y;
                }
                return cache_result;
};
24 голосов
/ 28 марта 2019

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

#include <iostream>

int main() {
    int x;
    int y;

    auto z = [&x, &y] { return x + y; }; // a lambda capturing x and y

    while(true) {
        std::cin >> x;
        std::cin >> y;
        std::cout << z() << "\n";
    }
}
20 голосов
/ 28 марта 2019

Есть два главных метода:

  1. Отсроченный расчет - вместо простой переменной z сделайте ее функцией, которая вычисляет значение по требованию (примеры см. В других ответах). Это может быть прозрачно для исходного кода, если z - это некоторый прокси-объект с неявным преобразованием в требуемый тип (как в ответ Аконкагуа ).

  2. Явное уведомление об изменениях. Это требует, чтобы x и y были наблюдаемыми типами; когда любое из них изменяет значение, z обновляет себя (и уведомляет своих наблюдателей, если это применимо).

Первая версия обычно предпочтительнее, но вторая может быть более подходящей, если вам нужно, чтобы z был наблюдаемым типом.

17 голосов
/ 29 марта 2019

Это звучит как XY проблема (каламбур).

Судя по всему, вы не пишете код в соответствии с хорошими объектно-ориентированными практиками.Я бы посоветовал вам не использовать «хитрости», предложенные другими людьми, но на самом деле узнать, как лучше использовать структуру ОО.

Прежде чем углубляться в это, обратите внимание, что назначение отличается от отношения равенства .= в C ++ - это присваивание, которое не совпадает с = в математике.Существует несколько (но не так много) языков программирования, которые поддерживают отношения равенства, но C ++ не является одним из них.Дело в том, что добавление поддержки отношений равенства порождает кучу новых проблем, поэтому это не так просто, как «почему этого еще нет в C ++».

В любом случае, в этом случае вам, вероятно, следует инкапсулироватьваши связанные переменные в классе.Затем вы можете использовать методы для получения «актуальной» информации.Например:

class Player {
    std::vector<int> inventory;
    int cash;
public:
    int inventory_total();
    int net_worth();
}

//adds up total value of inventory
int Player::inventory_total() {
    int total = 0;
    for(std::vector<int>::iterator it = inventory.begin(); it != inventory.end(); ++it) {
        total += *it;
    }
    return total;
}

//calculates net worth
int Player::net_worth() {
    //we are using inventory_total() as if it were a variable that automatically
    //holds the sum of the inventory values
    return inventory_total() + cash;
}


...


//we are using net_worth() as if it were a variable that automatically
//holds the sum of the cash and total holdings
std::cout << player1.net_worth();

Я допускаю, что добавить это поведение в класс немного сложнее, чем сказать z = x + y, но на самом деле это всего лишь несколько дополнительных строк кода.

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

В этом случае объект не имеет член net_worthпеременная, поэтому вы не можете случайно использовать ее вместо вызова функции.

8 голосов
/ 28 марта 2019
  1. Вы создаете для этого функцию.
  2. Вы вызываете функцию с соответствующими аргументами, когда вам нужно значение.

int z(int x, int y)
{
   return (x + y);
}


int x;
int y;

// This does ot work
// int z{x + y};

cin >> x;
cin >> y;
cout << z(x, y);
5 голосов
/ 29 марта 2019

Итак, большая проблема, которую я вижу с предоставленными лямбда-решениями, заключается в том, что z вычисляется каждый раз, когда проверяется , даже если ни x, ни y не изменилось . Чтобы обойти это, вам действительно нужно связать эти переменные. Я бы предложил сделать это через class:

class foo {
    int x;
    int y;
    int z;
    void calculate() { z = (x + y) / 2; }
    friend istream& operator >>(istream& lhs, foo& rhs);
public:
    void set_x(const int param) {
        x = param;
        calculate();
    }
    int get_x() const { return x; }
    void set_y(const int param) {
        y = param;
        calculate();
    }
    int get_y() const { return y; }
    int get_z() const { return z; }
};

istream& operator >>(istream& lhs, foo& rhs) {
    lhs >> rhs.x >> rhs.y;
    rhs.calculate();
    return lhs;
}

Это будет пересчитывать z каждый раз, когда устанавливается x или y. Это хорошее решение, если вы часто обращаетесь к z, а x и y устанавливаются нечасто. Если x и y установлены часто или calculate дорого, вы можете подумать:

class foo {
    int x;
    int y;
    int z;
    bool dirty;
    void calculate() { z = (x + y) / 2; }
    friend istream& operator >>(istream& lhs, foo& rhs);
public:
    void set_x(const int param) {
        x = param;
        dirty = true;
    }
    int get_x() const { return x; }
    void set_y(const int param) {
        y = param;
        dirty = true;
    }
    int get_y() const { return y; }
    int get_z() const { 
        if(dirty) {
            calculate();
        }
        return z;
    }
};

istream& operator >>(istream& lhs, foo& rhs) {
    lhs >> rhs.x >> rhs.y;
    rhs.dirty = true;
    return lhs;
}

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

foo xyz;

cin >> xyz;
cout << xyz.get_z();
5 голосов
/ 28 марта 2019

Вы можете определить следующую лямбду z, которая всегда возвращает текущее значение x+y, потому что x и y захвачены ссылкой:

DEMO

int main()
{
    int x;
    int y;

    const auto z = [&x, &y](){ return x+y; };

    std::cin  >> x; // 1
    std::cin  >> y; // 2
    std::cout << z() << std::endl; // 3

    std::cin  >> x; // 3
    std::cin  >> y; // 4
    std::cout << z() << std::endl; // 7
}
3 голосов
/ 02 апреля 2019

То, что вы описываете, это поздняя привязка , что компилируемый язык, такой как C ++, может делать только с трудом. В интерпретируемом языке все, что вам нужно, это возможность установить z в неоцененное выражение и задержать привязку значения z до тех пор, пока не понадобятся вычисления, как правило, сигнализируемые вызовом функции, которая вызывает оценку, такую ​​как eval в Lisp. На языке правил моей экспертной системы у меня есть не только eval, но и noeval, который защищает свои аргументы от одного уровня оценки. Это обеспечивает детальный контроль над связыванием, причем некоторые подвыражения оцениваются (связываются), а другие - нет, если это необходимо. Это не применимо к вашему сценарию, но устанавливает сцену с точки зрения языкового ландшафта.

3 голосов
/ 29 марта 2019

Вы можете получить то, что просите, используя макросы:

{
    int x, y;
#define z (x + y)
    /* use x, y, z */
#undef z
}

#undef для небольшого здравомыслия. Для большей рассудительности вообще не используйте макросы, используйте один из других ответов и разберитесь с лишним многословием.

Хотя класс с пользовательским operator int будет работать во многих случаях ... хм.

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