C ++ operator +和* non-const重载

时间:2010-03-11 11:42:35

标签: c++ operator-overloading const-correctness

我有以下棘手的问题:我已经实现了一个(相当复杂的)类,它以多小波为基础表示数学函数。由于+, - 和*之类的操作在这种情况下非常自然,我已经为这个类实现了重载运算符:

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,甚至可能返回一个const对象或引用。不幸的是,这是不可能的,因为在操作过程中所涉及的任何物体都是不变的!这可能听起来很奇怪,但这是数学所涉及的不可避免的后果。我可以伪造它,使用const_cast,但代码仍然是错误的!

有没有办法解决这个问题?我目前唯一的解决方案是使返回对象为const,从而有效地禁用操作符链接。

问候,。jonas。

5 个答案:

答案 0 :(得分:5)

如果你的对象是1GB(我猜他们在堆上分配的内存,而不是它们的实际sizeof大小),你可能不应该支持这些操作符。问题在于,运算符链接的示例或多或少地将不可变对象假定为“正在发生什么”的基本模型,并创建了许多临时值作为中间结果。您不能指望编译器能够有效地重用空间。但是你也不能无缘无故地复制1GB的对象。

相反,您应该只支持各种分配运算符。那么你的客户而不是写作:

y = a * b + c;

这可能会产生巨大的临时性,应该改写:

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

这样,用户可以对事物保持封闭。很容易看出没有创建临时代码,你不会意外地编写一行需要18GB运行的简单代码。如果你想这样做:

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的构造函数和析构函数没有可观察到的副作用,那么它跳过它们的能力仅限于标准中合法“复制省略”的特定情况。

或者您可以查看GMP的C接口,看看如何在没有运算符重载的情况下完成此操作。所有函数都采用“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.

答案 1 :(得分:2)

这个问题没有简单的解决办法。您的二元运算符(正确)生成无名临时对象 - 此类对象不能绑定到非const引用参数。

一种可能的方法是放弃操作符的链接 - 对于类X:

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

另一个(相当可怕的)解决方案是使类可变的数据成员 - 这样它们在逻辑上是常量但可以物理上可变的。然后,您将使参数成为const引用。就像我说的那样,这很糟糕,可能会引起其他问题。

答案 2 :(得分:1)

“......在操作过程中,所涉及的物体都没有变化!这可能听起来很奇怪......” 不,这听起来不奇怪,听起来不对。 如果您的操作员以可从外部观察的方式修改对象,则滥用操作符重载。 如果对象被修改,因为结果被缓存,使缓存变为可变,因此修改缓存的函数仍然可以被声明为const。

答案 3 :(得分:1)

您可以使用代理而不是实际值,代理可以是常量,因为它们不会被更改。下面是它的外观的一个小例子。请注意,所有临时工作仍将在该示例中创建,但如果您想要变得聪明,您可以保存操作,而不是实际的操作结果,并仅在有人想要最终获得结果或部分时进行计算它的。它甚至可能会极大地加快您的代码helped 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;
}

答案 4 :(得分:0)

按值而不是引用传递参数?

编辑:你可能想要使用+ = - = * =。