C++抽象类运算符重载和接口强制执行问题

26

(由原始帖子编辑以更改“BaseMessage”为“const BaseMessage&”)

大家好, 我对C++非常陌生,所以我希望你们能帮助我“看到我的错误”。

我有一个消息层次结构,并且我试图使用抽象基类来强制实施接口。 特别是,我想要强制每个派生的消息提供重载的 << 运算符。

当我尝试像这样做时:

class BaseMessage
{
public:

// some non-pure virtual function declarations
// some pure virtual function declarations

virtual ostream& operator<<(ostream& stream, const BaseMessage& objectArg) = 0;

}

编译器报错:

"error: cannot declare parameter ‘objectArg’ to be of abstract type ‘BaseMessage’

我认为这里还涉及到“friend”问题,但是当我尝试将它声明为:

virtual friend ostream& operator<<(ostream& stream, const BaseMessage objectArg) = 0;

编译器会添加一个额外的错误:

"error: virtual functions cannot be friends"

有没有一种方法可以确保我所有派生(消息)类都提供了"<<"输出流运算符?

非常感谢,

史蒂夫


Nilolai的答案是你想要实现的最佳解决方案。然而,你遇到的具体错误是因为你试图通过值传递一个BaseMessage对象(作为虚拟operator<<的第二个参数)。这是行不通的,因为BaseMessage包含一个纯虚函数(相同的虚拟operator<<),所以无法构造一个BaseMessage的实例来进行值传递。请注意,Nilolai版本的operator<<通过引用接受其第二个参数(它将是从Base派生的某个类)。 - Stephen C. Steel
5个回答

44

通常的惯例是在基本级别上拥有一个friend输出运算符,并让它调用私有虚函数:

class Base
{
public:

    /// don't forget this
    virtual ~Base();

    /// std stream interface
    friend std::ostream& operator<<( std::ostream& out, const Base& b )
    {
        b.Print( out );
        return out;
    }

private:

    /// derivation interface
    virtual void Print( std::ostream& ) const =0;
};

这是我在页面上看到的唯一有效的解决方案:其他人都在胡说八道 :) (尝试编译你们的代码吧!)+1 - jkp
这个很好用,谢谢Nikolai。我可以看到很多地方可以使用这个模式。-- Steve - Steve S.
我不知道你可以像那样直接在类声明中定义友元函数。好好知道!教科书总是把友元声明放在外面,定义放在里面。直接在类内部定义是否也适用于swap友元函数? - Emile Cormier

3

抽象类无法实例化,因此需要这样做:

virtual ostream& operator<<(ostream& stream, const Base &objectArg) = 0; 

虚函数必须是成员函数,而友元函数是非成员函数,因此不能将其声明为虚函数。

同样,静态函数也不能是虚函数,因为它是类方法而不是实例方法。

我的建议是:

class Base {
 public:
 virtual ostream&  print (ostream& stream) const = 0; 
};


class Derived :public Base {
 public:
 virtual ostream&  print (ostream& stream) const { //do something } 
};

ostream& operator <<(ostream& stream, const BaseMessage &objectArg) 
{
  return objectArg.print(stream); 
}

3

像流操作符这样的:

virtual ostream& operator<<(ostream& stream, const BaseMessage objectArg) = 0;

简单来说,一些函数不能成为类的成员函数,也不能成为虚函数。原因是当你这样声明时:

a << b;

你真正想说的是:
a.operator<<( b );

在这种情况下,a是一个流,而不是您的类的实例,因此运算符不能是您的类的成员。通常应该将它作为自由(非成员)函数,通过适当的成员函数访问您的类实例。

1
operator<<() 的声明是错误的。对于 op<< 的二元版本,您不必声明第二个参数——如果 op<< 是类的成员函数,则假定它为 this:
virtual ostream& operator<<(ostream& stream) = 0;
此外,正如其他人所提到的,流插入运算符必须是全局函数。将它们作为成员函数只会导致无法工作。
还有一些与您的问题无关的问题,但仍然是一个问题。在您的原始实现中,您通过值传递了一个抽象基类对象,而不是通过引用或指针传递。当您这样做时,您“切片”了对象。我相信您的意图是将多态类型的基类指针传递给函数,然后让函数以多态方式调用方法。例如,您试图做类似于以下内容的事情:
#include <cstdio>
#include <string>
#include <iostream>
using namespace std;

class Base 
{
public:
    virtual void dump() 
    {
        cout << "Base";
    };
};

class Der : public Base
{
public:
    void dump()
    {
        cout << "Der";
    };
};

void DumpIt(Base b)
{
    b.dump();
}


int main() 
{
    Der obj;
    DumpIt(obj);
    return 0;

}

...并期望输出为“Der”。但实际上,由于Object Slicing,输出是“Base”。因为DumpIt()函数按值接受Base对象,所以基于原始对象创建了一个新的临时Base对象。为了获得您期望的功能,您需要通过引用或指针传递:

void DumpIt(Base & b)
{
    b.dump();
}

这个函数的输出是“Der”。


0

这里的问题在于"BaseMessage objectArg"表示应该按值传递objectArg对象。

这是不可能的,因为您使用纯虚调用使类变为抽象类。通过引用传递"BaseMessage& objectArg"或通过指针传递"BaseMessage* objectArg"会使此错误消失。

按值传递意味着使用复制构造函数创建变量的新实例。由于您确保无法创建BaseMessage的实例,因此无法执行此操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接