如何在抽象基类中正确地重载运算符?

8
假设我有一个抽象基类,它只定义了一个容器,可以在其上执行加法操作:
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 对象有意义。
无论如何,感觉就像是咬自己的尾巴,有没有正确的解决方案?
更新:根据迄今为止的答案,似乎我使用了错误的模式。我想将接口与实现分离,以便库代码只需要知道接口,而客户端代码提供实现。我试图通过提供抽象基类作为接口,以及子类作为实现来实现这一点。

更新2:我的问题实际上包含两个问题,一个具体的问题(有关在抽象类中重载运算符),以及另一个有关我的意图的问题(如何允许客户端自定义实现)。前者已得到答复:不要这样做。对于后者,似乎我使用的接口类模式是解决该问题的好方法(根据Griffiths and Radford),只是不应该涉及重载运算符。

6个回答

8

最好不要这样做。

operator+ 返回一个值,而抽象类型的定义不允许返回值。只为具体类型重载运算符,并避免从具体类型继承,以防止“通过重载运算符进行切片”。

将对称二元运算符(如operator+)作为自由函数进行重载,您可以控制哪些类型的组合可以合理地组合,并相应地防止类型对象的组合没有意义。

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


我更新了问题以澄清我的意图:唯一有意义的加法情况是针对2个派生对象,但库代码只知道接口(即Base),而不知道实现(即Derived)。 - AnonymousCoward
@AnonymousCoward:我已经看过你的更新,但我认为它并没有影响到我所说的。如果接口的客户端不知道“Derived”的存在,那么它就不能通过值来接收一个新的“Derived”对象,因此您必须使用指向“Base”的指针或引用。 - CB Bailey
好的,那么如果我不能再依靠虚拟成员函数来实现这一点,我该如何确保库调用客户提供的Add()函数呢? - AnonymousCoward
@AnonymousCoward:谁说不能使用虚成员了?你可以像这样编写代码:std :: auto_ptr <Base> Base :: Add(const Base&base)const;。需要做一些工作才能使所有情况下的 a.Add(b) 等同于 b.Add(a),但这肯定是一个方法。 - CB Bailey

2
通常的解决方案是Jim Coplein的代数层次结构模式。请参见以下链接:
Coplein的原始论文(特别是代数层次结构部分):Coplein's original paper Wikibooks: Wikibooks 简要说明一下,您需要一个具体的包装类,它持有指向实际派生类对象的多态指针。将所需的操作符定义为转发到底层表示,同时保留您要查找的值语义(而不是对象语义)。确保该包装类使用RAII习惯用法,以免每个临时对象泄漏内存。

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

class Derived: public Base<Derived> {
public:

可能会起作用(除了不完整类的问题)。


1
在这个解决方案中,每个派生类都是从不同的Base<>类派生而来,这有点违背了多态的思想。 - Amnon
@Ammom 是的,没错。但是我以前也用过类似的技巧,不过目的不同。 - Anycorn

0
我想将接口与实现分离,这样库代码只需要知道接口,客户端代码提供实现。我尝试通过提供抽象基类作为接口和子类作为实现来实现这一点。
使用pimpl惯用语
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)要求每个程序执行仅使用一个基类的派生类(仍然使用pimpl作为编译防火墙,例如用于交换共享库),或者3)要求派生类知道如何处理通用基类或将抛出异常。

0

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

那么只剩下指针了,但这会非常奇怪。还有整个问题,当你不再需要指针时如何释放它。使用智能指针可能是解决该问题的一种方法,但你仍然面临 "operator+ 返回指针" 的问题。

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


-1

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

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

这只有在C++/CLI中才可用,是吗? - J.N.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接