【问题标题】:How to get attributes of subclasses with base class pointer如何使用基类指针获取子类的属性
【发布时间】:2013-03-29 18:02:01
【问题描述】:

我有一个问题,我以类似的方式一次又一次地遇到。

例如:

我有一个抽象基类,它充当一系列具体类的接口,比如说,数据容器。

class DataInterface
{
public:
    DataInterface();
    ~DataInterface();

    virtual void FetchData(void) = 0;
    virtual void ProcessData(void) = 0;
    virtual void ClearData(void) = 0;
}

具体的类如下所示:

class BinaryData: public DataInterface
{
public:
    BinaryData();
    ~ BinaryData();

    virtual void FetchData(void);
    virtual void ProcessData(void);
    virtual void ClearData(void);

private:
    bool m_boolData;
}

class IntegerData: public DataInterface
{
public:
    IntegerData();
    ~ IntegerData();

    virtual void FetchData(void);
    virtual void ProcessData(void);
    virtual void ClearData(void);

private:
    int m_intData;
}

子类实现它们从 DataInterface 继承的接口。但是他们有不同的属性来保存他们的数据。到目前为止一切顺利。

我可以像这样使用主函数中的类:

int main()
{
    int IntegerData;
    bool BoolData;
    DataInterface *pData1 = new BinaryData();
    DataInterface *pData2 = new IntegerData();  

    pData1->FetchData();
    pData2->FetchData();

    pData1->ProcessData();
    pData2->ProcessData();

    // now I want to get the data of pData1 and pData2, for example to write it into a file, show in visualization, ...
    IntegerData = pData2->GetData() ????
    BoolData = pData1->GetData() ????
}

现在问题来了:

如何从具体类中获取数据?我只有基类指针,所以我需要在 DataInterface 中定义一个抽象的 getter 方法。但是 getter 方法的签名会因子类而异。比如一次我需要返回一个整数,一次我需要返回一个布尔类型。

请给我一个提示,这个问题让我抓狂:/

【问题讨论】:

    标签: c++ class inheritance subclass virtual


    【解决方案1】:

    在每个派生类上创建一个非虚拟GetData() 成员。然后,如果您确定您的对象的实际类,您可以简单地执行静态转换并调用GetData()

    int intData = static_cast<IntegerData*>(pData2)->GetData();
    

    如果您不知道该类,则需要执行动态转换并检查其结果:

    if (IntegerData* _pData2 = dynamic_cast<IntegerData*>(pData2))
    {
      int intData = _pData2->GetData();
      // Do stuff with the int
    }
    else if (BinaryData* _pData2 = dynamic_cast<BinaryData*>(pData2))
    {
      bool binaryData = _pData2->GetData();
      // Do stuff with the bool
    }
    

    【讨论】:

    • 嗯。通常我不知道基类指针指向的实际类,因为我使用工厂类。您的动态铸造方法很有趣。我想知道这是不是很好的做法? :)
    • 通常你要避免dynamic_cast;它有一些开销,并且通常表明您的界面设计存在缺陷。但是,如果您在创建后很快就失去了对对象实际类型的了解,那么您就无能为力了。除非您知道工厂创建对象时数据的目的地,否则您可以使用 icepack 的 `SendData' 方法。
    【解决方案2】:

    如果您想将数据传递给另一个实体,则需要对其进行抽象。 有两种常见的方法来实现这一点:

    1: 使用void*

    class DataInterface
    {
    public:
       ...
       virtual void* GetData() = 0;
    };
    
    class BinaryData: public DataInterface
    {
    public:
       virtual void* GetData() { return &m_boolData; }
    
    private:
        bool m_boolData;
    };
    

    main 中这样使用:

    int main()
    {
        bool BoolData;
        DataInterface *pData1 = new BinaryData();
    
        pData1->FetchData();
        pData1->ProcessData();
        BoolData = *(bool*))pData1->GetData());
    }
    

    这种方法的优点在于简单。 缺点是直接访问对象的内部(破坏封装)以及滥用多态性(如果最终强制转换为与具体派生相关的类型,为什么还需要接口?)

    2:

    一种更稳健的方法是不将原始数据从您的具体对象发送到客户端,而是使与客户端的通信成为对象的附加角色。

    class DataInterface
    {
    public:
       ...
       virtual void SendData() = 0;
    };
    
    class BinaryData: public DataInterface
    {
    public:
       ...
       virtual void SendData() 
       {
            //do your stuff here, you know the exact type of your data
       }
    };
    
    int main()
    {
        bool BoolData;
        DataInterface *pData1 = new BinaryData();
    
        pData1->FetchData();
        pData1->ProcessData();
        pData1->SendData();
    }
    

    注意,这是一个非常简单的例子,但它展示了这个想法。通常,在实际用例中,您会向您的类注册客户端并通过定义的接口将数据发送给它们。

    【讨论】:

      【解决方案3】:

      我不太确定这是一种“好的”做法,但这是解决此问题的一种方法。 这样做的一个好处是,如果您尝试获取错误类型的数据,您可以获得自定义错误消息。而且你可以避免演员表(我不是他们的粉丝)。

      class DataInterface
      {
      public:
        DataInterface();
        ~DataInterface();
      
        virtual void FetchData(void) = 0;
        virtual void ProcessData(void) = 0;
        virtual void ClearData(void) = 0;
        virtual int getIntData() { // Error message }
        virtual bool getBoolData() { // Error message }
      };
      
      class BinaryData: public DataInterface
      {
      public:
          BinaryData();
          ~ BinaryData();
      
          virtual void FetchData(void);
          virtual void ProcessData(void);
          virtual void ClearData(void);
          virtual int getIntData() { // Error message }
          virtual bool getBoolData() { return m_boolData; }
      
      private:
          bool m_boolData;
      }
      
      class IntegerData: public DataInterface
      {
      public:
          IntegerData();
          ~ IntegerData();
      
          virtual void FetchData(void);
          virtual void ProcessData(void);
          virtual void ClearData(void);
          virtual int getIntData() { return m_intData; }
          virtual bool getBoolData() { // Error message }
      
      private:
          int m_intData;
      }
      
      
      int main()
      {
          int IntegerData;
          bool BoolData;
          DataInterface *pData1 = new BinaryData();
          DataInterface *pData2 = new IntegerData();  
      
          pData1->FetchData();
          pData2->FetchData();
      
          pData1->ProcessData();
          pData2->ProcessData();
      
          // now I want to get the data of pData1 and pData2, for example to write it into a file, show in visualization, ...
          IntegerData = pData2->GetIntData();
          BoolData = pData1->GetBoolData();
          BoolData = pData2->GetBoolData() // This will tell you that you are trying to get bool from int class.
      }
      


      这是使用模板处理它的一种方法。

      using namespace std;
      
      template<typename T>
      class DataInterface
      {
      public:
          DataInterface(T d) : data(d) {}
      
          virtual T GetData() = 0;
      protected:
          T data;
      };
      
      class BinaryData : public DataInterface<bool>
      {
      public:
          BinaryData(bool b) : DataInterface<bool>(b) {} 
      
          virtual bool GetData() {return data;}
      
      };
      
      class IntegerData: public DataInterface<int>
      {
      public:
          IntegerData(int i) : DataInterface<int>(i) {} 
      
          virtual int GetData() {return data;}
      
      };
      
      
      int main()
      {
          int myint;
          bool mybool;
          DataInterface<bool> *pData1 = new BinaryData(true);
          DataInterface<int> *pData2 = new IntegerData(1);  
      
      
          // now I want to get the data of pData1 and pData2, for example to write it into a file, show in visualization, ...
          myint = pData2->GetData();
          mybool = pData1->GetData();
      
          cout<<myint<<" "<<mybool<<endl;
      }
      

      【讨论】:

      • 感谢您的回答。但我认为这是相当糟糕的做法,因为接口类现在对其子类具有依赖关系。每次添加新的子类(例如新的数据类型)时,都需要修改接口类。
      • 没错。您需要对具体的类有所了解。这取决于您的需要。
      • 顺便说一句,您还可以将基类实现为模板类,并为每个子类专门化它。
      • 你能给我一个简单的例子吗?
      • 请注意,模板方法要求您知道处理 DataInterface 指针的每个点的数据类型。而且您不能再将不同类型的对象粘贴到标准容器中。如果这不是问题,这是更有效的选择。
      【解决方案4】:

      实现这一点的一个非常简单的方法是设计您的基类,使其返回一个变体类型。变体是一个可区分的联合容器,它保存来自一组异构类型的对象(请参阅http://www.boost.org/doc/libs/1_59_0/doc/html/variant.html)。这是一个完整的例子:

      #include <iostream>
      #include <algorithm>
      #include <boost/variant.hpp>
      
      #include <memory>
      
      using namespace std;
      
      class DataInterface
      {
      public:
          DataInterface(){};
          virtual ~DataInterface(){};
      
          virtual void FetchData(void) = 0;
          virtual void ProcessData(void) = 0;
          virtual void ClearData(void) = 0;
          virtual boost::variant<bool,int,double,std::string> GetData()=0;
      };
      
      class IntResult : public DataInterface{
      public:
          IntResult() : resultInt(0){};
          ~IntResult(){};
      
          virtual void FetchData() override {resultInt = 10;};
          virtual void ProcessData() override {resultInt *= 10;}
          virtual void ClearData() override {resultInt = 0;};
      
          virtual boost::variant<bool,int,double,std::string> GetData()override{
              return resultInt;
          };
      
      private:
          int resultInt;
      };
      
      
      class StringResult : public DataInterface{
      public:
          StringResult() : resultString(""){};
          ~StringResult(){};
      
          virtual void FetchData() {
              resultString= "Hello World";
          }
      
          virtual void ProcessData() override {
              std::transform(resultString.begin(), resultString.end(),resultString.begin(), ::toupper);
          }
      
          virtual void ClearData() override {resultString = "";}
      
          virtual boost::variant<bool,int,double,std::string> GetData() override {
              return resultString;
          };
      
      
      private:
          std::string resultString;
      };
      
      
      
      
      
      int main() {
      
          DataInterface* data;
      
          IntResult* intResult = new IntResult;
          StringResult* stringResult = new StringResult;
      
          data = intResult;
      
          data->FetchData();
          data->ProcessData();
      
          switch(data->GetData().which()){
              case 0:
                  std::cout << "found bool: " << boost::get<bool>(data->GetData()) << std::endl;
                  break;
              case 1:
                  std::cout << "found int: " << boost::get<int>(data->GetData()) << std::endl;
                  break;
              case 2:
                  std::cout << "found double: " << boost::get<double>(data->GetData()) << std::endl;
                  break;
              case 3:
                  std::cout << "found string: " << boost::get<std::string>(data->GetData()) << std::endl;
                  break;
              default:
                  break;
          }
      
          data = stringResult;
      
          data->FetchData();
          data->ProcessData();
      
          switch(data->GetData().which()){
              case 0:
                  std::cout << "found bool: " << boost::get<bool>(data->GetData()) << std::endl;
                  break;
              case 1:
                  std::cout << "found int: " << boost::get<int>(data->GetData()) << std::endl;
                  break;
              case 2:
                  std::cout << "found double: " << boost::get<double>(data->GetData()) <<  std::endl;
                  break;
              case 3:
                  std::cout << "found string: " << boost::get<std::string>(data->GetData()) << std::endl;
                  break;
              default:
                  break;
          }
      
          delete intResult;
          delete stringResult;
      
          return 0;
      }
      

      请注意,在您的情况下,bool 可以隐式转换为 int,因此您可以始终简单地返回 int。如果您需要返回真正的异构类型,则变体方法将起作用。等效地,您可以返回 boost any,它还可以让您统一操作类型的异构联合(请参阅http://www.boost.org/doc/libs/1_59_0/doc/html/any.html)。最后,如果您不希望对 boost 有任何依赖,那么推出您自己的变体类型并不难,它可以保存不同的类型集。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-02-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多