C++抽象基类,具有纯虚拟运算符+函数

C++ Abstract Base Class with pure virtual operator+ function

本文关键字:虚拟 运算符 函数 抽象 基类 C++      更新时间:2023-10-16

假设我正在创建一个抽象基类,用于定义公共接口。我希望operator+成为这个公共接口的一部分。这在C++中似乎是不可能的,或者我缺少了一些东西。

例如,假设下面的代码是一个.h文件,声明名为IAbstract的抽象基类的公共接口。此代码不编译,因为我无法声明返回抽象基类实例的函数。这个编译错误是有道理的,但我不明白的是:有什么方法可以保证运算符+存在于IAbstract的公共接口中吗

#include <iostream>
// An abstract base class
class IAbstract
{
public:
IAbstract(int value);
virtual ~IAbstract() {}
virtual int SomeOtherFunction() =0;
//This is ok
virtual IAbstract& operator++() =0;
//This will not compile
virtual const IAbstract operator+(const IAbstract& rhs) =0;
//Xcode5 says "Semantic Issue: Return type 'IAbstract' is an abstract class"
};

除了按值返回的问题外,设计的真正问题是C++语言的工作方式。

像Java这样的面向对象语言通过类层次结构、接口和多态性来定义和传输功能。您的代码就是典型的例子
C++不能以这种方式工作。C++通过抽象概念定义功能,类只需要实现这些概念就可以以某种方式使用。类如何/是否遵循或实现某个概念取决于其行为、某些函数的实现(如重载运算符)等。

因此,实现您想要实现的目标的C++方法是以通用的方式重载所需的运算符,以确保所有实现该概念的类都能使用它
换句话说,如果您试图创建一个可添加的概念,即表示可以与其他对象一起添加的内容,则类只需重载operator+。如果一个类重载了operator+的可添加项。很简单。

下面是另一个例子:std::copy():的经典实现

template<typename IT , typename DEST_IT>
void copy( IT begin , IT end , DEST_IT dest_begin )
{
while( begin != end )
*dest_begin++ = *begin++;
}

在该实现中,beginend的类型是什么简单的回答:我们不知道。可以是任何东西但可以是必须满足我们函数要求的任何东西,即:是可引用的、可增加的和可比较的。换句话说:是一个可迭代的类型。一个工作(看起来像)迭代器的类型。

所以std::copy()适用于由可迭代事物表示的任何范围。可能是阵列:

int main()
{
int a[] = { 1,2,3 };
int b[3];
int* begin_a = &a[0];
int* end_a   = &a[2];
int* begin_b = &b[0];
//Pointers are an iterable thing, right?:
std::copy( begin_a , end_a , begin_b );
//Or just the common generic way (Which does exactly the same):
std::copy( std::begin( a ) , std::end( a ) , std::begin( a ) );
}

矢量、列表等:

int main()
{
std::vector<int> a = { 1,2,3 };
std::vector<int> b;
//Wooh, the code is exactly the same! Thats why templates are cool.
std::copy( std::begin( a ) , std::end( a ) , std::begin( a ) );
}

更奇怪的事情,比如遍历流的迭代器:

int main()
{
std::vector<int> a = { 1,2,3 };
//Copy a vector to the standard output? Yes we can!
std::copy( std::begin( a ) , std::end( a ) , std::ostream_iterator<int>( std::cout , "n" ) );
}

1
2
3

以及您自己的类:

struct numeric_iterator
{
int value;
numeric_iterator( int initial_value = 0 ) : value( initial_value ) {}
//numeric_iterator is an iterable thing because...
//... its dereferenciable
int operator*() const
{
return value;
}
//... its incrementable
numeric_iterator& operator++()
{
++value;
return *this;
}
numeric_iterator operator++(int)
{
numeric_iterator copy( *this );
++(*this);
return copy;
}
//... and its comparable
friend bool operator==( const numeric_iterator& lhs , const numeric_iterator& lhs )
{
return lhs.value == rhs.value;
}
friend bool operator!=( const numeric_iterator& lhs , const numeric_iterator& lhs )
{
return !( lhs == rhs );
}
};
int main()
{
//Tah dah!
std::copy( numeric_iterator( -4 ) , 
numeric_iterator(  5 ) , 
std::ostream_iterator<int>( std::cout , " " ) 
);
}

-4-3-2-1 0 1 2 3 4

有问题的行是:

virtual const IAbstract operator+(const IAbstract& rhs) =0;

该方法按值返回,从而复制并创建类型为IAbstract的对象,这是不允许的。您应该更改为:

virtual const IAbstract& operator+(const IAbstract& rhs) =0;

这似乎是你的意图,但很容易疏忽。

您拥有的某个位置:IAbstract variable,但您永远不可能真正拥有IAbstract,只有指向它的指针(或引用)。

它很生气,因为在某个地方你试图处理一个IAbstract

它希望IAbstract*IAbstract&IAbstract&&都是好的(以某种形式(常量和易失性等))。

现在,您将从运算符++返回IAbstract,这毫无意义,因为您可以永远拥有IAbstract,只需将某些内容视为一个即可。

否,不能保证运算符+将存在于IAbstract派生类的公共接口中,因为您不能正确地将运算符+声明为纯虚拟函数。

诀窍是将抽象基类封装在一个正则类型中。该解决方案要求有一种克隆方法

class AbstractNumericType
{
public:
virtual AbstractNumericType& operator+=(AbstractNumericType const& other) = 0;
virtual std::unique_ptr<AbstractNumeric> clone() = 0;
virtual ~AbstractNumericType() = default;
};
class NumericType
{
public:
template<class Something>
NumericType(Something value):m_obj{createNumber(value)}
{}
NumericType(NumericType const& other): m_obj{other.m_obj->clone()}
{}
NumericType operator+(NumericType const& other) const
{
auto ret = *this;
(*ret.m_obj) += (*other.m_obj);
return ret;
}
private:
std::unique_ptr<AbstractNumericType> m_obj;
};

演示:https://gcc.godbolt.org/z/e4v1Tr

这里真正的问题是,当没有可用的具体类型时,如何实现operator+=。您可以添加多个重载(如访问者模式),也可以使用基于rtti的类型切换。这些解决方案都不是很好。