动态上下文相关运算符的设计模式(例如模块化算术)?

时间:2014-07-24 12:07:29

标签: c++ design-patterns operator-overloading language-design

这是我经常遇到的某种软件工程和语言设计问题,因为我没有任何语言的良好解决方案。我对C ++解决方案最感兴趣,但其他(希望有词汇范围)语言的解决方案也值得考虑。

这是一个例子。让我们说我有一些代码,就像这样:

template<class T, class F>
T foo(T a, T b, T c, T d, F func) { return func() / (a * d - b * c); }

我认为调用者应该能够使用foo进行模运算以及常规算术。

换句话说,对于finite_field的适当定义,在理想世界中,应该在有限域而不是在实数域中评估上面的代码: / p>

int main(int argc, char *argv[])
{
    finite_field<int> scoped_field(argc /* let's say this is my modulus */);
    return foo(1, 2, 3, 4, []{ return +1; }) + foo(4, 3, 2, 1, []{ return -1; });
}

然而,很明显,这不起作用,因为foo内部没有任何内容(具体而言,没有任何操作符)知道scoped_field强加的算术上下文。

所有我所知道的解决方案非常难看:

  1. 完全停止使用算术运算符 使用add(x, y)代替x + ydiv(x, y)代替x / y,等等 然后可以将所有这些放在某种类的Arithmetic类中,并使用this来访问当前的&#34;算术上下文&#34;。

    优点:它有效,并且不需要存储多余的数据。

    缺点:需要编辑foo,这可能不是必需的,同时使其不那么令人愉悦,也更难以阅读和写作。

  2. 定义包装ModInt的自定义int类型,将模数存储在每个数字中,并重载该类型的运算符以从中读取模数其中一个输入参数。

    优点:它有效,并且不需要修改foo的正文。

    缺点:低效且容易出错 - 每个模数存储在每个整数内,这意味着在运行时可能存在冲突错误,以及明显的O(n)空间低效率。更不用说评估上下文不是数字的属性,而是运营商本身的属性。

  3. 存储&#34;当前上下文&#34;在一个线程局部变量中,并根据上下文重载操作符以表现不同。

    优点:它有效(有点)。它不会浪费空间或需要修改foo

    缺点:丑陋,不太便携,不可重复或容易出错,具体取决于它是如何实现的(它会污染被调查者和算术运算符上下文)

  4. 所以,我实际上并不知道任何可读,可移植,可维护的解决方案
    据我所知,似乎我根本不得不放弃其中一个。


    我的问题:

    1. 这是一个常见或众所周知的问题吗?

    2. 它是否在任何相当流行的语言中都有一个优雅的解决方案?如果是,那些,以及如何?

    3. 可以用C ++专门解决吗?是否有某种设计模式或成语?

2 个答案:

答案 0 :(得分:0)

  1. 不。
  2. 不知道。尝试标记每种合理流行的语言。我确定没有人会遇到问题。
  3. 为什么在上帝的名字中应该有一种模式或成语来涵盖每个人在一种语言中可能找到的每一个问题?为什么每个解决方案都会突然变成一种模式或成语?它只是解决问题的方法吗?严重。
  4. 尽管如此,foo的一个小修改可能会产生预期的结果。

    template<typename T, typename F, typename F2>
    T foo(T a, T b, T c, T d, F func, F2 mod) { return func() / (mod(a) * d - mod(b) * c); }
    
    int main(int argc, char *argv[])
    {
        auto mod = [](int i) { return ModInt(i, modulus); };
        return foo(1, 2, 3, 4, []{ return +1; }, mod) + foo(4, 3, 2, 1, []{ return -1; }, mod);
    }
    

    由于mod的每次使用都使用相同的本地,因此ModInt无法使用不同的模数。并且由于mod仅在运算符优先级时被调用,并且需要它并且任何本地人都被销毁并且它们的存储被重新使用,所以我们就是金色的w.r.t.储存空间。 (另外,严肃地说,堆栈上有几个整数?没什么大不了的。)

    但从本质上讲,模运算所涉及的运算符的属性。这就是它的定义方式和工作原理。你可以破解它,但无论你做什么,它至少会吮吸一点。您需要一个用户定义的运算符,但是您不能隐式地为两个基本类型传递用户定义的函数,而且您也不能。

答案 1 :(得分:0)

希望我能正确理解你的问题。简而言之,我猜你想在运行时决定一组重载算术运算的行为。这样的事情会起作用吗?

struct Arithmetic;

struct MyInt {
    int value;
    MyInt operator+(const MyInt& other);
    static void StandardArithmetic();
    static void ModularArithmetic();
    static Arithmetic* arithmetic;
};

struct Arithmetic { virtual MyInt Add( MyInt, MyInt ) const = 0; };

struct StandardArithmetic : public Arithmetic {
    virtual MyInt Add( MyInt a, MyInt b) const {
        MyInt result = {a.value + b.value};
        return result;
    }
};

struct ModularArithmetic : public Arithmetic { /* ... */ };

.cpp

Arithmetic * MyInt::Arithmetic = 0;
void MyInt StandardArithmetic() {
    delete arithmetic;
    arithmetic = new StandardArithmetic;
}
/* ... */
MyInt MyInt::operator+(const MyInt& other) const {
    return arithmetic->add(*this, other);
}

使用此功能,您可以通过调用MyInt::Arithmetic轻松切换不同类型的算术。您也可以将算术指针存储在例如抽象基类的静态成员,如果要实现各种算术运算,这将是有意义的。