在抽象基类中重载运算符的正确方法是什么?

时间:2011-02-01 20:08:05

标签: c++ operator-overloading polymorphism abstract-class

假设我有一个抽象基类,它只定义了一个可以在其上执行加法的容器:

class Base {
public:
    virtual ~Base() {}
    virtual Base operator+(const Base& rhs) =0;
};

然后我想要Base的子类来提供实际的操作:

class Derived: public Base {
public:
    Base operator+(const Base& rhs) { // won't compile
        // actual implementation
    }
};

这是我的问题:operator +()应该返回一个新的Base对象,但是Base是抽象的,它不会编译。

我尝试通过使用工厂返回对Base对象的引用来解决这个问题,但是在运算符的主体中我发现自己正在进行转换,因为加法仅对Derived对象有意义。

在任何情况下,感觉我都在咬自己的尾巴,有没有合适的解决方案呢?

更新:根据目前为止的答案,我似乎使用了错误的模式。我想将接口与实现分开,这样库代码只需要知道接口和客户端代码提供的实现。我尝试通过将接口作为抽象基类提供,并将实现作为子类来实现。

UPDATE2:我的问题实际上是2个问题,一个具体的问题(关于在抽象类中重载运算符)和另一个关于我的意图(我如何允许客户端自定义实现)。前者已被回答:不要。对于后者,似乎我使用的接口类模式实际上是解决该问题的好方法(根据Griffiths and Radford),这只是我不应该混淆重载运算符。

6 个答案:

答案 0 :(得分:8)

最好的事情不是。

operator+返回一个值,根据定义,您不能返回抽象类型的值。仅为具体类型重载操作符,并避免从具体类型继承以防止“由重载操作符切片”。

将像operator+这样的对称二元运算符重载为自由函数,您可以控制哪些类型的组合可以合理组合,反之则可以防止组合对象无意义的对象组合。

如果您有通过两个基类引用执行“添加”并创建新对象的有效方法,则必须通过指针,引用或指针包装智能对象返回。因为您无法保留+的常规语义,所以我建议使用命名函数,例如Add()而不是使用“令人惊讶”的语法制作operator+

答案 1 :(得分:2)

通常的解决方案是Jim Coplein的代数层次模式。请参阅以下内容:

Coplein's original paper(特别是Algebraic Hierarchy

Wikibooks

详细说明一下,你基本上需要一个具体的包装类,它包含一个指向实际派生类对象的多态指针。定义有问题的运算符以转发到底层表示,同时保留您正在寻找的值语义(而不是对象语义)。确保包装器类使用RAII惯用法,这样就不会泄漏每个临时对象的内存。

答案 2 :(得分:1)

template<class C>
class Base {
public:
    virtual ~Base() {}
    virtual C operator+(const C& rhs) =0;
};

class Derived: public Base<Derived> {
public:

可能有效(除非不完整的课程问题)。

答案 3 :(得分:0)

Base是抽象的,你不能实例化它,所以按值返回是不可能的。这意味着你必须通过指针或引用返回。通过引用返回是危险的,因为operator+可能返回临时值。

通过指针离开,但这将是非常奇怪的。并且存在如何在不再需要时释放指针的整个问题。使用智能指针可能是解决该问题的方法,但您仍然可以让整个“operator+返回指针”问题。

那么,你想要实现什么目标? Base代表什么?添加Base子类的两个实例是否有意义?

答案 4 :(得分:0)

  

我想将接口与实现分开,因此库代码只需要知道接口和客户端代码提供的实现。我尝试通过将接口作为抽象基类提供,并将实现作为子类来实现。

使用pimpl idiom

struct Base {
  virtual ~Base() = 0;
  virtual std::auto_ptr<Base> clone() = 0;
  virtual void add(Base const &x) = 0;  // implements +=
};

struct UserInterface {
  UserInterface() : _pimpl(SomeFactory()) {}
  UserInterface(UserInterface const &x) : _pimpl(x._pimpl->clone()) {}

  UserInterface& operator=(UserInterface x) {
    swap(*this, x);
    return *this;
  }

  UserInterface& operator+=(UserInterface const &x) {
    _pimpl->add(*x._pimpl);
  }

  friend void swap(UserInterface &a, UserInterface &b) {
    using std::swap;
    swap(a._pimpl, b._pimpl);
  }

private:
  std::auto_ptr<Base> _pimpl;
};

UserInterface operator+(UserInterface a, UserInterface const &b) {
  a += b;
  return a;
}

要实际实现Base :: add,你需要1)双重调度和处理产生的重载爆炸,2)要求每个程序执行只使用一个派生类的base(仍然使用pimpl作为编译防火墙,例如用于交换共享库),或3)要求派生类知道如何处理通用基础或将抛出异常。

答案 5 :(得分:-1)

使用动态类型和基本类型。 在基类中枚举派生类是不可能的,因为运算符重载必须始终是静态的。最方便的选择是在重载参数中使用'dynamic'类型。以下是一个例子。

public class ModelBase
{
    public abstract static string operator +(ModelBase obj1, dynamic obj2)
    {
        string strValue = "";
        dynamic obj = obj1;
        strValue = obj.CustomerID + " : " + obj2.CustomerName;
        return strValue;
    }
}