在运算符中动态强制转换为派生类型<<

Dynamic cast to derived type in operator<<

本文关键字:lt 派生 类型 转换 运算符 动态      更新时间:2023-10-16

我有一个基类Tag和一个子类TagSet,它继承自Tag

class Tag
{
  public:
    Tag(std::string);
    std::string tag;
};
std::ostream & operator <<(std::ostream &os, const Tag &t);
class TagSet : public Tag
{
public:
    TagSet();
};
std::ostream & operator <<(std::ostream &os, const TagSet &ts);

及其实现

Tag::Tag(std::string t)
: tag(t)
{}
std::ostream & operator <<( std::ostream &os, const Tag &t )
{
  os << "This is a tag";
  return os;
}
TagSet::TagSet()
: Tag("SET")
{}
std::ostream & operator <<(std::ostream &os, const TagSet &ts)
{
  os << "This is a TagSet";
  return os;
}

我想包括一个具有成员std::vector<Tag*>的第三类TagList,它可以容纳Tag*实例或TagSet*实例。我想为TagList定义<<运算符,以便如果元素是Tag,则它使用operator<<Tag版本,如果元素是TagSet,则使用operator<<TagSet版本。这是我的尝试:

std::ostream & operator <<(std::ostream &os, const TagList &ts)
{
  for (auto t : ts.tags)
  {
    if (t->tag == "SET")
    {
      TagSet * tset = dynamic_cast<TagSet*>(t);
      os << *tset << ", ";
    }
    else os << t->tag << ", ";
  } 
}

代码在运行时崩溃。我检查了tset指针,它不为空。可能是一个糟糕的演员阵容。

正确的方法是什么?问题是否与operator<<函数中的常量有关?欢迎就如何实现这一目标提出其他建议。

TagList实现的其余部分是为了完整起见:

class TagList
{
public:
    TagList(std::vector<Tag*> taglist);
    std::vector<Tag*> tags;
    typedef std::vector<Tag*>::const_iterator const_iterator;
    const_iterator begin() const { return tags.begin(); }
    const_iterator end() const { return tags.end(); }
};
std::ostream & operator <<(std::ostream &os, const TagList &ts);

TagList::TagList(std::vector<Tag*> tagvec)
: tags(tagvec.begin(), tagvec.end())
{}

如果我可以建议一个不同的解决方案来解决输出Tag对象的问题,那么只有一个运算符重载,对于Tag const&,然后在Tag结构中调用虚拟output函数。然后在继承的类中重写该函数。

也许像

struct Tag
{
    ...
    virtual std::ostream& output(std::ostream& out)
    {
        return out << "This is Tagn";
    }
    friend std::ostream& operator<<(std::ostream& out, Tag const& tag)
    {
        return tag.output(out);
    }
};
struct TagSet : Tag
{
    ...
    std::ostream& output(std::ostream& out) override
    {
        return out << "This is TagSetn";
    }
};

然后输出列表

for (auto t : ts.tags)
    std::cout << *t;

你不能这样做,因为operator<<不是虚拟的。

定义一个虚拟print方法,并在operator<<中使用它,例如

class Tag {
public:
    virtual void print(std::ostream &f) const;
};
std::ostream & operator <<(std::ostream &os, const Tag &t)
{
    t->print(os);
    return os;
}

现在,您也可以在TagList中使用方法print(),而无需任何强制转换:

std::ostream & operator <<(std::ostream &os, const TagList &ts)
{
    for (auto t : ts.tags) {
        t->print(os);
        os << ", ";
    }
}

或隐式

for (auto t : ts.tags) {
    os << *t << ", ";
}

您当前的方法本质上是手写的 RTTI,具有额外的内存开销。使用动态强制转换将其与内置 RTTI 混合将不起作用,因为类 TagTagSet 不是多态的,并且这些类型的对象的内置 RTTI 在运行时不可用来执行此类转换。如果您坚持使用手写RTTI,则需要执行static_cast

TagSet & tset{static_cast<TagSet &>(*t)};