C++抽象基类,具有纯虚拟运算符+函数
C++ Abstract Base Class with pure virtual operator+ function
假设我正在创建一个抽象基类,用于定义公共接口。我希望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++;
}
在该实现中,begin
和end
的类型是什么简单的回答:我们不知道。可以是任何东西但可以是必须满足我们函数要求的任何东西,即:是可引用的、可增加的和可比较的。换句话说:是一个可迭代的类型。一个工作(看起来像)迭代器的类型。
所以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的类型切换。这些解决方案都不是很好。
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- C++:从抽象类重写纯虚拟运算符重载
- 虚拟超载运算符>>和<<
- 冲突的 CLANG"虚拟 dtor"和"已弃用的复制运算符"警告
- 如何将运算符重载为虚拟运算符
- 使用范围解析运算符时,在构造函数中调用虚拟方法是否安全?
- 基类和派生类的虚拟插入运算符重载
- 重载纯虚拟运算符
- 具有虚拟赋值运算符的多态性
- C++ 中的纯虚拟赋值运算符
- 超级多重非虚拟继承中基类的作用域运算符
- 是否可以完全模拟虚拟运算符++(int)?
- C++抽象基类,具有纯虚拟运算符+函数
- 虚拟运算符重载
- 如何在c++中覆盖此虚拟运算符
- C++-虚拟运算符=在派生实例的基类上调用
- 为什么在基类而不是派生类上调用虚拟运算符==
- 在派生类中重新定义虚拟运算符==
- C++ 重载虚拟 + 运算符
- 重载虚拟运算符 -> ()