类似虚拟的朋友功能?

Virtual-like friend functions?

我想创建类似

的界面
class Scalar {
public:
  Scalar() {}
  virtual ~Scalar() {}
  //virtual members operators
  virtual Scalar& operator+() const = 0;
  virtual const Scalar operator-() const;
  virtual Scalar& operator=() = 0;
  virtual Scalar& operator+=() = 0;
  //...
};

我也打算使用一些朋友功能,例如:

    friend const Scalar operator+(const Scalar&, const Scalar&);

但是当我导出抽象class和创建导出class时出现问题,说:

class RealNumber: public Scalar {
public:
  friend const RealNumber operator+(const RealNumber&, const RealNumber&);
  //some definitions...
};

根据这个逻辑,我需要为从 Scalar 派生的每个新 class 定义一个新的 friend operator+ 重载。有什么方法可以解决这个问题并避免在所有派生的 classes 中声明这些朋友吗?

这是你的问题吗?

我了解到您的问题是您的两个朋友引用了完全不同的函数,因为它们具有不同的签名:

friend const Scalar operator+(const Scalar&, const Scalar&);
friend const RealNumber operator+(const RealNumber&, const RealNumber&);

更糟糕的是,class 外部友元的选择不会是多态的:将根据编译时类型选择合适的友元。

如何解决?

首先,您可以考虑覆盖 class 本身的运算符(保持签名相同),而不是使用外部重载的朋友。

然而,这有两个主要挑战:

  • 几乎不可能 return 来自算术运算符的引用,除非您接受副作用,这会使您的运算符的行为与预期不同。
  • 您需要处理不同类型标量的组合:如果您必须将 Scalar 添加到 RealNumber 怎么办?这将需要实施 double dispatch 来应对所有可能的组合。

所以,死胡同了?

不,还有其他两种选择,具体取决于您的实际问题:

  • 您真的要在 运行 时动态组合算术类型吗?如果是,则需要放弃 C++ 运算符覆盖方法,并使用 interpreter pattern.
  • 实现表达式求值器
  • 如果不是,请考虑使用基于模板的设计,以便编译器在编译时选择适当的专业化。
  • 或者因为许多可能的朋友及其参数类型的组合而受苦。

您可能无法创建虚拟好友函数,但您可以创建虚拟运算符(即使 operator + 也可以这样做)。

考虑以下代码:警告:这根本不是好的设计

#include <iostream>
using namespace std;

class AA {
private:
    int a;
public:
    AA(int a): a(a) {};
    inline int getA() const { return a; };
    virtual AA operator +(const AA &a) {
        AA res(this->getA() + a.getA());
        return res;
    }
};

class BB: public AA {
    public:
    BB(int a): AA(a) {}
    virtual AA operator +(const AA &a) {
        AA res(this->getA() - a.getA());
        return res;
    }
};

int main() {
    BB tmp(1);
    AA& a = tmp;
    AA b(7);
    cout << (a + b).getA();
    return 0;
}

当我写这段代码的时候,我发现可以引入很多缺陷(比如真正做减法的 + 运算符,还有如果第二个操作数是 BB 而不是第一个操作数怎么办??)

所以,关于你的问题,你需要标量。所以你可以采用以下方法:

#include <iostream>
using namespace std;

// Here, Scalar acts as an abstract class, all its goal is to convert your
// class type into some calculable value type (e.g. you can use T as double)
template <typename T>
class Scalar {
public:
    // Converter function is abstract
    virtual operator T() = 0;
};

class AA: public Scalar<double> {
private:
    double a;
public:
    inline double getA() {return a;};
    AA(double a): a(a) {}
    // Implements the converter function in Scalar, T in scalar is mapped
    //    to double because we did Scalar<double>
    virtual operator double() {
        return a;
    }
};

class BB: public Scalar<double> {
private:
    int a;
public:
    inline double getA() {return (double)a;};
    BB(int a): a(a) {}
    virtual operator double() {
        return (double)a;
    }
};

int main() {
    BB tmp(1);
    AA b(7);
    // Here, it is easy for us just to add those, they are automatically converted into doubles, each one like how it is programmed.
    cout << (b + tmp);
    return 0;
}