类似虚拟的朋友功能?
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;
}
我想创建类似
的界面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;
}