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

发布于 2024-10-15 17:07:53 字数 888 浏览 4 评论 0原文

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

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 对象的引用来解决这个问题,但随后在运算符的主体中我发现自己正在进行强制转换,因为添加仅对派生对象有意义。

无论如何,感觉就像是在咬自己的尾巴,有没有适当的解决方案?

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

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

Suppose I have an abstract base class, that just defines a container on which addition can be performed:

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

Then I want subclasses of Base to provide the actual operation:

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

Here is my problem: operator+() is supposed to return a new Base object, but Base being abstract it won't compile.

I tried to get around that by using a factory to return a reference to a Base object, but then in the body of the operator I find myself doing casts, because the addition only makes sense on Derived objects.

In any case, it feels like I am biting my own tail, is there a proper solution to this?

UPDATE: Based on the answers so far, it seems I am using the wrong pattern. I want to separate the interface from the implementation, so that library code only has to know the interface and client code provides the implementation. I tried to do that by providing the interface as an abstract base class, and the implementation as subclasses.

UPDATE2: My question was actually 2 questions, a concrete one (about overloading operators in abstract classes) and another about my intent (how do I allow the client to customize the implementation). The former has been answered: don't. For the latter, it seems that the Interface Class pattern I use is actually a good one to solve that problem (according to Griffiths and Radford), it's just that I should not mess with overloaded operators.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

北凤男飞 2024-10-22 17:07:53

最好的办法就是不要。

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

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

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

The best thing is not to.

operator+ returns a value and you can't return a value of an abstract type, by definition. Overload the operators only for concrete types and avoid inheriting from concrete types to prevent "slicing by overloaded operator".

Overload symmetric binary operators like operator+ as free functions and you can control which combinations of types can be sensibly combined, and conversely prevent the combination of objects of types for which the combination doesn't make sense.

If you have a valid way of performing an "add" via two base class references and creating a new object you will have to return via a pointer, reference or pointer-wrapping smart object. Because you can't preserve the conventional semantics of + I would recommend using a named function, e.g. Add() instead of making an operator+ with a "surprising" syntax.

‘画卷フ 2024-10-22 17:07:53

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

Coplein 的原始论文(特别是代数层次结构)

Wikibooks

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

The usual solution to this is Jim Coplein's Algebraic Hierarchy pattern. See the following:

Coplein's original paper (particularly Algebraic Hierarchy)

Wikibooks

To elaborate a bit, you essentially need a concrete wrapper class that holds a polymorphic pointer to the actual derived class object. Define the operators in question to forward to the underlying representation while preserving the value semantics (rather than object semantics) you're looking for. Make sure that wrapper class uses the RAII idiom so you don't leak memory with every temporary object.

清风疏影 2024-10-22 17:07:53
template<class C>
class Base {
public:
    virtual ~Base() {}
    virtual C operator+(const C& rhs) =0;
};

class Derived: public Base<Derived> {
public:

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

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

class Derived: public Base<Derived> {
public:

might work (barring imcomplete class problem).

枯叶蝶 2024-10-22 17:07:53

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

那是通过指针离开的,但这会很奇怪。整个问题是当您不再需要指针时如何释放它。使用智能指针可能是该问题的解决方案,但您仍然遇到整个“operator+ 返回指针”问题。

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

Base being abstract, you cannot instantiate it, so returning by value is out of the question. That means you'll have to return by pointer or reference. Returning by reference is dangerous since operator+ is likely to return a temporary value.

That leaves by pointer, but that would be very weird. And there's the whole question of how to release the pointer when you no longer need it. Using a smart pointer might be a solution to that problem, but you still have the whole "operator+ returns a pointer" issue.

So, what are you trying to achieve? What does Base represent? Does it make sense to add two instance of a Base subclasses?

拥醉 2024-10-22 17:07:53

我想将接口与实现分开,以便库代码只需要知道接口,而客户端代码提供实现。我尝试通过提供接口作为抽象基类,并将实现作为子类来实现这一点。

使用 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) double调度并处理由此产生的过载爆炸,2) 要求每个程序执行仅使用一个基派生类(仍然使用 pimpl 作为编译防火墙,例如用于交换共享库),或 3) 要求派生类知道如何处理通用基数,否则会抛出异常。

I want to separate the interface from the implementation, so that library code only has to know the interface and client code provides the implementation. I tried to do that by providing the interface as an abstract base class, and the implementation as subclasses.

Use the 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;
}

To actually implement Base::add, you'll need either 1) double dispatch and dealing with the resulting overload explosion, 2) require that only one derived class of base is used per program execution (still using pimpl as a compilation firewall, e.g. for swapping out shared libraries), or 3) require derived classes know how to deal with a generic base or will throw an exception.

番薯 2024-10-22 17:07:53

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

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

use the dynamic type and base type.
in the base class to enumerate derived class is impossible since the operator overloading must be static at all time. The most convenience option would be to use 'dynamic' type in the overloading arguments. Following is an example.

public class ModelBase
{
    public abstract static string operator +(ModelBase obj1, dynamic obj2)
    {
        string strValue = "";
        dynamic obj = obj1;
        strValue = obj.CustomerID + " : " + obj2.CustomerName;
        return strValue;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文