Development Tip

변수를 항상 계산 결과와 같게 만들려면 어떻게해야합니까?

yourdevel 2020. 12. 9. 21:51
반응형

변수를 항상 계산 결과와 같게 만들려면 어떻게해야합니까?


수학에서, 경우 z = x+y/2, 다음 z항상 우리의 가치를 교체 할 때마다 변경됩니다 xy. and z의 값을 변경할 때마다 특별히 업데이트하지 않고도 프로그래밍에서 그렇게 할 수 있습니까 ?xy

그런 식으로 작동하지 않을 것입니다.

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

왜 이것이 필요한지 혼란 스러우면 변수를 라이브로 표시하고 rhs- 변수가 변경 될 때 자동으로 업데이트되도록합니다.

크립을 죽이고 금을 얻을 때와 마찬가지로 순 가치 (현금 + 자신의 아이템 가치)가 변경되었습니다. 또는 운전하는 속도에 따라 자동차의 속도계가 달라집니다.


편집 : 질문에 완전히 대답했지만 Artelius대답살펴보십시오 . 내 답변이 제공하지 않는 몇 가지 문제 (캡슐화, 중복 방지, 댕글 링 참조 위험)를 해결합니다. 계산 비용이 많이 드는 경우 가능한 최적화는 Jonathan Mee답변에 나와 있습니다.


당신은 다음과 같은 것을 의미합니다.

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

클래스는 int로 캐스팅 될 때까지 결과 계산을 지연합니다. 캐스트 연산자는 명시 적이 지 않으므로 Zint가 필요할 때마다 사용할 수 있습니다. operator<<int 대한 오버로드가 있으므로 예를 들어 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;

그러나 표시되지 않더라도 여전히 함수 호출 (캐스트 연산자의 암시 적 호출) 이 있다는 점에 유의하십시오 . 그리고 실제로 연산자는 내부 멤버에 액세스하는 것보다 실제 계산을 수행하므로 함수 호출을 숨기는 것이 정말 좋은 생각인지 의문입니다.


C ++에서 람다를 사용하여 이에 근접 할 수 있습니다. 일반적으로 다음과 같은 변수를 설정하면

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

zx + 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;
};

가장 가까운 방법은 펑터를 만드는 것입니다.

#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";
    }
}

두 가지 주요 기술이 있습니다.

  1. 지연된 계산- z단순한 변수가 아니라 필요에 따라 값을 계산하는 함수로 만드십시오 (예는 다른 답변 참조). z필요한 유형에 대한 암시 적 변환이있는 프록시 객체 인 경우 소스 코드 투명성이 될 수 있습니다 ( Aconcagua의 답변 에서와 같이 ).

  2. 명시적인 변경 알림. 이 필요 x하고 y관찰 할 수있는 유형으로, 둘 중 하나가 값을 변경하면 z자체적으로 업데이트됩니다 (해당되는 경우 관찰자에게 알립니다).

일반적으로 첫 번째 버전이 선호되지만 z관찰 가능한 유형 이 필요한 경우 두 번째 버전이 더 적절할 수 있습니다 .


이것은 XY 문제 (의도 된 말장난) 처럼 들립니다 .

그 소리에서, 당신은 좋은 객체 지향 관행에 따라 실제로 코드를 작성하지 않습니다. 다른 사람들이 제안한 "트릭"을 사용하지 말고 실제로 OO 구조를 더 잘 사용하는 방법을 배우는 것이 좋습니다.

들어가기 전에 할당평등 관계와 구별됩니다 . =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 실수로 함수를 호출하는 대신 사용할 수 없도록, 멤버 변수를.


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

다음 람다 정의 할 수 있습니다 z항상의 현재 값을 반환 x+y하기 때문 xy참조로 캡처를 :

데모

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
}

그래서 제가 제공하는 람다 솔루션에서 볼 수있는 큰 문제는 둘 다 변경 되지 않았 더라도z 검사 xy 때마다 계산된다는 것입니다 . 이 문제를 해결하려면 이러한 변수를 연결해야합니다. 다음을 통해 수행하는 것이 좋습니다 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자주 설정됩니다. 경우 xy자주 설정되거나 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();

매크로를 사용하여 원하는 것을 얻을 있습니다 .

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

The #undef is for a little sanity. For more sanity, don't use macros at all, and go with one of the other answers, and deal with the extra verbosity.

Although a class with a custom operator int would work in a lot of cases ... hmm.


What you're describing is late binding, which a compiled language like C++ can do only with difficulty. In an interpreted language, all you need is the ability to set z to an unevaluated expression and delay binding of z's value until the calculation is needed, typically signaled by a call to a function that forces the evaluation such as eval in Lisp. In my Expert System's rules language, I have not only eval but noeval, which protects its argument from one level of evaluation. That provides granular control over the binding, with some sub-expressions being evaluated (bound) and others not, if desired. This is not applicable to your scenario, but it sets the scene in terms of the language landscape.


You could write a class that encapsulates its state to update either when mutated or return the right result when requested :

#include <iostream>

template<typename T, typename U, typename V>
class DynamicCalc
{
public:
    DynamicCalc(const T& func, const U& memberOne, const V& memberTwo) :
        _func(func)
      , _memberOne(memberOne)
      , _memberTwo(memberTwo)
    {

    }

    void SetMemberOne(const U& memberOne) { _memberOne = memberOne; }
    void SetMemberTwo(const U& memberTwo) { _memberTwo = memberTwo; }
    auto Retrieve() { return _func(_memberOne, _memberTwo); }

    U GetMemberOne() { return _memberOne; }
    V GetMemberTwo() { return _memberTwo; }

private: 
    T _func;

    U _memberOne;
    V _memberTwo;
};

int main() {

    auto func = [](int x, int y) {
        return x + y;
    };
    DynamicCalc<decltype(func), int, int> c(func, 3, 5);

    c.SetMemberOne(5);
    std::cout << c.Retrieve();
}

In truth, if you're happy for the calculation to happen when the value is reuqested then the getters/setters are unnecessary.


Ok, let me at last write the right and only true answer to your stated question:

You can't.

You can't write z = x + y and then have all the code using z magically re-run whenever x or y changes.

So what can be done?

As mentioned in other answers, there are several patterns to express that you want changes of x and y to cause some updates, but in any case you need these updates to happen more or less explicitly.

Depending on a use case, you may:

  • Have the value recomputed anyway at all times this matters. E.g. if you write a game and redraw the screen every frame, then likely just making sure that you don't accidentally keep the z value between the frames is enough. Be aware of when your value can change and when it can't. Whether you use a function, a lambda, a class method, or just repeat the expression, is mostly esthetical decision. If available, this is the best approach, because it is fully transparent.

    For instance, in racing game you'd likely update your current speed at the beginning of the new tick computation, and then use the updated value when computing your car's movement, when redrawing the speed indicator, when creating the motion blur, and so on. You don't need any magic and not even a function, you can just use a variable, because you know your speed won't change during one frame.

  • Call the update explicitly. Use it e.g. when you have a single widget you need to update. Downside is that you need to remember to call the update, which is somewhat brittle, but on the upside - it is dead simple. A middle ground is to have the update call integrated with a setter, making it kind of poor man's Observer implementation.

  • Use Observer pattern (see also signals and slots, this is one way of implementing Observer). Use it e.g. when you have many widgets to update, or you create them dynamically. Avoid using it when one of the above works, they are way simpler.

  • Use dedicated reactive programming library. As such stuff exists, I feel obliged to mention it. However, I honestly don't see any application where I would use it. It mostly seems like a complicated way to shoot your feet. The implicit updates are going to backfire, and you'll have to rewrite everything. Just don't, not in C++. What is important: while this approach is closest to "magically update everything", it would impose constraints on how you write your code, and eventually you'll get one of the above solutions, just more complicated.

참고URL : https://stackoverflow.com/questions/55402807/how-can-i-make-a-variable-always-equal-to-the-result-of-some-calculations

반응형