通过C++基类指针访问派生类成员

4
有没有办法通过基类指针访问派生类成员的通用代码?或者有其他方法解决这个问题吗?
假设我有一个形状类Shape。我有Square和Triangle类继承它。两者都有自己的私有成员,与对方无关,因此在基类中没有必要拥有它们。现在,如果我需要将一个类写入文件,但在写入文件之前我不知道这个类是Square还是Triangle怎么办?
我一直在试图解决这个问题。最坏的情况是将Square和Triangle的数据都写入文件,并为读取和写入添加标识符(Triangle或Square),然后在加载数据时使用小型解析器组合类。这样会效率低下,浪费时间。
我想知道是否有一些技巧、设计模式或任何可以帮助解决这种情况的东西。

2
你指的是策略设计模式吗?这个想法是将writeToFile()方法定义为抽象(纯虚函数),并让派生类来实现它。 - amit
也许你应该先读一本C++书籍... - Walter
5个回答

8

这个序列化过程应该使用虚函数来完成。在基类中定义一个函数,负责序列化对象。三角形和正方形重写此方法并写入:

  • 标识符
  • 需要序列化的所有数据

如果合适,您可以在基类中实现公共部分。

当您想要加载文件时,您将需要一个工厂方法来创建相应标识符的类实例。新实例的虚拟反序列化方法必须被调用以加载实际数据。


1
这会完全破坏多态的目的,当然你也可以在使用typeid检查类型后将其强制转换为Square*等。 - Dan
@Dan 他为什么想要进行强制类型转换? - harper
他在谈论如何知道它是指向正方形还是三角形对象的指针。只是为了完整起见,我想提一下。 - Dan
感谢您的答案。它似乎是我所需要的,但很遗憾它不起作用。我正在使用Boost Serialization,它使用模板来进行序列化功能,这与虚函数不兼容。 - user1646394

3
您可以在基类中定义一个纯虚拟的getter,所有派生类都将覆盖它。代码如下:
class Shape{
  public:
    virtual int data() const = 0;
};
class Square: public Shape{
  private:
     int _member;
  public:
    virtual int data() const{
     //do something with private members and return it
     return _member;
    };
};

这假设你只想要一个数据,并且它总是相同类型的。不太灵活。 - Dan
然后你需要使用模板。但无论发生什么,每当你请求数据时,你都知道它应该返回什么类型。 - Neel Basu

1

我认为没有直接的方法可以消除这种开销。通常有两种方法来解决这个问题。首先,对象需要一个序列化机制:

为了序列化事物,需要一个位置来进行序列化。在这种情况下,我们将使用数据容器来进行序列化,但也可以是文件流或容器类。序列化可以从对象内部或外部进行,最简单的实现方式现在是从内部进行:

简单的序列化部分:

class Shape{
public:
  virtual void serialize( Datacontainer &o ) const = 0;
};

class Triangle: public Shape{
  void serialize( Datacontainer &o ) const{
    o.add('T');
    o.add(corners);
  }
  std::vector<point> corners;
}

class Circle: public Shape{
  void serialize( Datacontainer &o ) const{
    o.add('C');
    o.add(center);
    o.add(radius);
  }
  point center;
  double radius;
}

在序列化期间,您可以使用基本类Shape来完成此操作:
Shape *tri = new Triangle;
tri->serialize( dataContainer or file );

反序列化并不容易,因为你需要知道类型。一个好的模式是建造者模式。尽管如此,我们可以实现一种更C++风格的方法来做到这一点:

将以下内容添加到所有类中:

static Shape* createFrom( Datacontainer &o );

例如,Circle 的实现方式:
Shape* Circle::createFrom( Datacontainer &o )
{
  Circle *c = new Circle()
  c->center = o.get();
  c->radius = o.get();
}

这使我们能够创建一个具体的实例,但方法具有共同的函数脚印。现在,我们可以实现一个非常简单的构建器,如下所示:
class ShapeBuilder
{
public:
  static Shape* createShape( Datacontainer& o )
  {
    char id = o.get();
    swith(id){
    case 'T':
      return Triangle::createFrom(o);
    case 'C':
      return Circle::createFrom(o);
    }        
  }
}

0
你需要在基类中声明虚方法,并让派生类定义它们。但如果你想将它们保存到文件中,你需要一种方式来识别文件中的特定类实例,因为它们可能具有不同的内存布局。

0

可能最好的方法是这样做。基本模式是,您可以将通用代码放在基类中,这些代码保证对于每个派生类始终相同。需要有所不同的内容,请放在虚函数中,由每个派生类分别实现。

class Shape {
    virtual void writeSerialData(std::ostream &) const = 0;
  public:
    void writeToFile(const std::string &filename) const {
        std::ofstream outfile(filename); // filename.c_str() in C++03
        writeSerialData(outfile);
        if (!outfile.close()) {
            // report the error
        }
    }
    virtual ~Shape() {}
};

class Square : public Shape {
    double length;
    virtual void writeSerialData(std::ostream &out) const {
        out << "Square{" << length << '}';
    }
  public:
    Square(double l) : length(l) {}
};

现在你面临的下一个问题是——如何在不事先知道派生类是什么的情况下,从文件中读取一个对象?为此,你需要一种方法来查看文本Square,并且要么(a)调用类Square的静态函数来解释数据,要么(b)通过将数据传递给它来实例化类Square并解释数据。在深入研究之前,值得考虑使用Boost Serialization或其他序列化库。

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