簡體   English   中英

在C ++中存儲任意對象的列表

[英]Storing a list of arbitrary objects in C++

在Java中,可以有一個對象列表。 您可以添加多種類型的對象,然后檢索它們,檢查其類型並對該類型執行適當的操作。
例如:(很抱歉,如果代碼不完全正確,我要從內存中刪除)

List<Object> list = new LinkedList<Object>();

list.add("Hello World!");
list.add(7);
list.add(true);

for (object o : list)
{
    if (o instanceof int)
        ; // Do stuff if it's an int
    else if (o instanceof String)
        ; // Do stuff if it's a string
    else if (o instanceof boolean)
        ; // Do stuff if it's a boolean
}

在C ++中復制此行為的最佳方法是什么?

boost::variant類似於dirkgently關於boost::any的建議,但支持Visitor模式,這意味着以后添加特定於類型的代碼更加容易。 而且,它在堆棧上分配值,而不是使用動態分配,從而導致代碼效率更高。

編輯:正如litb在評論中指出的那樣,使用variant而不是any方式,您只能持有一種預定類型列表中的值。 這通常是一個優勢,盡管在問詢者的情況下可能是劣勢。

這是一個示例(雖然不使用Visitor模式):

#include <vector>
#include <string>
#include <boost/variant.hpp>

using namespace std;
using namespace boost;

...

vector<variant<int, string, bool> > v;

for (int i = 0; i < v.size(); ++i) {
    if (int* pi = get<int>(v[i])) {
        // Do stuff with *pi
    } else if (string* si = get<string>(v[i])) {
        // Do stuff with *si
    } else if (bool* bi = get<bool>(v[i])) {
        // Do stuff with *bi
    }
}

(是的,從技術上講,您應該使用vector<T>::size_type而不是int來表示i的類型,並且無論如何,您應該從技術上使用vector<T>::iterator ,但是我正在嘗試使其保持簡單。)

您使用Boost.Variant和訪問者的示例:

#include <string>
#include <list>
#include <boost/variant.hpp>
#include <boost/foreach.hpp>

using namespace std;
using namespace boost;

typedef variant<string, int, bool> object;

struct vis : public static_visitor<>
{
    void operator() (string s) const { /* do string stuff */ }
    void operator() (int i) const { /* do int stuff */ }
    void operator() (bool b) const { /* do bool stuff */ }      
};

int main() 
{
    list<object> List;

    List.push_back("Hello World!");
    List.push_back(7);
    List.push_back(true);

    BOOST_FOREACH (object& o, List) {
        apply_visitor(vis(), o);
    }

    return 0;
}

使用此技術的好處是,如果稍后在變量中添加了另一種類型,而又忘記修改訪問者以包括該類型,則它將無法編譯。 必須支持所有可能的情況。 而如果您使用switch或級聯if語句,則很容易忘記在各處進行更改並引入錯誤。

C ++不支持異構容器。

如果您不打算使用boost那么hack就是創建一個虛擬類,並使所有不同的類都從該虛擬類派生。 創建一個您選擇的容器來容納虛擬類對象,您就可以開始了。

class Dummy {
   virtual void whoami() = 0;
};

class Lizard : public Dummy {
   virtual void whoami() { std::cout << "I'm a lizard!\n"; }
};


class Transporter : public Dummy {
   virtual void whoami() { std::cout << "I'm Jason Statham!\n"; }
};

int main() {
   std::list<Dummy*> hateList;
   hateList.insert(new Transporter());
   hateList.insert(new Lizard());

   std::for_each(hateList.begin(), hateList.end(), 
                 std::mem_fun(&Dummy::whoami));
   // yes, I'm leaking memory, but that's besides the point
}

如果要使用boost ,可以嘗試boost::any 是使用boost::any的示例。

您可能會發現兩位感興趣的著名C ++專家撰寫的這篇出色的文章

現在,正如j_random_hacker所提到的, boost::variant是另一件事。 因此,這是一個比較,以使您對使用什么有一個清晰的認識。

使用boost::variant ,上面的代碼如下所示:

class Lizard {
   void whoami() { std::cout << "I'm a lizard!\n"; }
};

class Transporter {
   void whoami() { std::cout << "I'm Jason Statham!\n"; }
};

int main() {

   std::vector< boost::variant<Lizard, Transporter> > hateList;

   hateList.push_back(Lizard());
   hateList.push_back(Transporter());

   std::for_each(hateList.begin(), hateList.end(), std::mem_fun(&Dummy::whoami));
}

這種事情實際上多久有用一次? 我從事C ++編程已經有很多年了,在不同的項目上,並且從未真正想要過異構容器。 由於某種原因,它在Java中可能很常見(我對Java的了解要少得多),但是對於Java項目中對它的任何給定用法,可能有一種方法可以做一些不同的事情,而這些事情在C ++中會更好地工作。

C ++比Java更加注重類型安全,這是非常不安全的類型。

就是說,如果對象沒有共同之處,為什么要將它們存儲在一起?

如果它們確實有共同點,則可以為它們創建一個類以使其繼承; 或者,使用boost :: any。 如果它們繼承,則可以調用虛擬函數,或者如果確實需要,可以使用dynamic_cast <>。

我只想指出,使用動態類型轉換來基於類型進行分支通常會暗示體系結構中的缺陷。 大多數時候,您可以使用虛函數實現相同的效果:

class MyData
{
public:
  // base classes of polymorphic types should have a virtual destructor
  virtual ~MyData() {} 

  // hand off to protected implementation in derived classes
  void DoSomething() { this->OnDoSomething(); } 

protected:
  // abstract, force implementation in derived classes
  virtual void OnDoSomething() = 0;
};

class MyIntData : public MyData
{
protected:
  // do something to int data
  virtual void OnDoSomething() { ... } 
private:
  int data;
};

class MyComplexData : public MyData
{
protected:
  // do something to Complex data
  virtual void OnDoSomething() { ... }
private:
  Complex data;
};

void main()
{
  // alloc data objects
  MyData* myData[ 2 ] =
  {
    new MyIntData()
  , new MyComplexData()
  };

  // process data objects
  for ( int i = 0; i < 2; ++i ) // for each data object
  {
     myData[ i ]->DoSomething(); // no type cast needed
  }

  // delete data objects
  delete myData[0];
  delete myData[1];
};

遺憾的是,在C ++中沒有簡單的方法可以做到這一點。 您必須自己創建一個基類,並從該類派生所有其他類。 創建基類指針的向量,然后使用dynamic_cast(帶有其自身的運行時開銷)查找實際類型。

僅出於完整性考慮,我想提到的是,您實際上可以通過使用void *使用純C進行此操作,然后將其強制轉換為必需的內容(好吧,我的示例不是純C,因為它使用了矢量,但這可以節省我一些代碼)。 如果您知道對象是什么類型,或者將字段存儲在記住該字段的位置,則此方法將起作用。 您當然可以不想這樣做,但是以下示例說明了這樣做的可能性:

#include <iostream>
#include <vector>

using namespace std;

int main() {

  int a = 4;
  string str = "hello";

  vector<void*> list;
  list.push_back( (void*) &a );
  list.push_back( (void*) &str );

  cout <<  * (int*) list[0] << "\t" << * (string*) list[1] << endl;

  return 0;
}

雖然無法將原始類型存儲在容器中,但是可以創建原始類型包裝器類,這些類類似於Java的自動裝箱的原始類型(在您的示例中,實際上是自動裝箱的原始類型文字); 實例會以原始的變量/數據成員的形式出現在C ++代碼中(並且(幾乎)可以使用)。

請參閱具有C ++中面向對象設計模式的數據結構和算法中 的內置類型的對象包裝器

使用包裝的對象,您可以使用c ++ typeid()運算符比較類型。 我很確定以下比較將起作用: if (typeid(o) == typeid(Int)) [其中Int將是int基本類型的包裝類,等等...](否則,只需將一個函數添加到您的返回typeid的原始包裝器,從而返回: if (o.get_typeid() == typeid(Int)) ...

話雖如此,就您的示例而言,這對我來說有代碼味。 除非這是唯一一個檢查對象類型的地方,否則我傾向於使用多態性(特別是如果您還有其他特定於類型的方法/功能)。 在這種情況下,我將使用原始包裝器添加一個接口類,該接口類聲明將由每個包裝的原始類實現的延遲方法(用於執行“做東西”)。 有了它,您將能夠使用容器迭代器並消除if語句(同樣,如果您僅具有這一類型的比較,那么僅使用多態來設置延遲方法就太過分了)。

我是一個沒有經驗的人,但是這就是我要去的地方-

  1. 為您需要處理的所有類創建一個基類。
  2. 編寫容器類/重用容器類。 (在看到其他答案后進行了修訂-我之前的觀點太含糊了。)
  3. 編寫類似的代碼。

我相信有更好的解決方案是可能的。 我也相信可能會有更好的解釋。 我了解到我有一些不良的C ++編程習慣,因此我嘗試不使用代碼就傳達我的想法。

我希望這有幫助。

正如大多數人所指出的那樣,除了事實之外,您不能這樣做,或者更重要的是,您確實不希望這樣做。

讓我們忽略您的示例,並考慮更接近實際示例的內容。 具體來說,我在一個真正的開源項目中看到了一些代碼。 它試圖在字符數組中模擬cpu。 因此,根據操作碼,它將在數組中放入一個字節的“操作碼”,然后是0、1或2個字節,該字節可以是字符,整數或指向字符串的指針。 為了解決這個問題,它涉及很多麻煩。

我的簡單解決方案:4個單獨的堆棧<> s:一個用於“ opcode”枚舉,一個用於chars,int和string。 將下一個從操作碼堆棧中取出,然后將使用其他三個中的哪個來獲取操作數。

您的實際問題很有可能以類似的方式處理。

好了,您可以創建一個基類,然后創建從其繼承的類。 然后,將它們存儲在std :: vector中。

簡短的答案是...您不能。

長答案是...您必須定義自己的新繼承對象,這些繼承都從基礎對象繼承。 在Java中,所有對象最終都來自“對象”,這就是允許您執行此操作的原因。

C ++中的RTTI(運行時類型信息)一直很困難,尤其是交叉編譯器。

最好的選擇是使用STL並定義一個接口以確定對象類型:

public class IThing
{
   virtual bool isA(const char* typeName);
}

void myFunc()
{
   std::vector<IThing> things;

   // ...

   things.add(new FrogThing());
   things.add(new LizardThing());

   // ...

   for (int i = 0; i < things.length(); i++)
   {
       IThing* pThing = things[i];

       if (pThing->isA("lizard"))
       {
         // do this
       }
       // etc
   }
}

麥克風

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM