在C ++中存储任意对象的列表

时间:2009-03-03 18:01:36

标签: c++ collections types

在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 ++中复制此行为的最佳方法是什么?

13 个答案:

答案 0 :(得分:20)

boost::variant类似于dirkgently对boost::any的建议,但支持访问者模式,这意味着以后更容易添加特定于类型的代码。此外,它在堆栈上分配值而不是使用动态分配,从而导致代码效率稍高。

编辑:正如litb在评论中指出的那样,使用variant代替any意味着您只能保留一个预先指定的类型列表中的值。这通常是一种力量,尽管这可能是提问者案件中的一个弱点。

这是一个例子(虽然不使用访客模式):

#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,但我是试图保持简单。)

答案 1 :(得分:14)

使用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或cascading if语句,很容易忘记在任何地方进行更改并引入错误。

答案 2 :(得分:13)

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::anyHere是使用boost::any

的示例

您可能会感兴趣的两位领先的C ++专家发现这个优秀的article

现在,boost::variant是另一件需要注意的事项j_random_hacker。所以,这里有一个comparison来了解使用什么。

使用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));
}

答案 3 :(得分:12)

这种事情多久有用一次?我已经用C ++编程了很多年,在不同的项目上,从来没有真正想要一个异类容器。由于某种原因,它可能在Java中很常见(我的Java经验要少得多),但是对于Java项目中任何给定的使用,可能有一种方法可以做一些在C ++中更好的工作。

C ++比Java更重视类型安全,而且这种类型不安全。

那就是说,如果对象没有任何共同点,为什么要将它们存储在一起?

如果他们确实有共同点,你就可以为他们做一个继承的课程;或者,使用boost :: any。如果它们继承,则具有要调用的虚函数,或者使用dynamic_cast&lt;&gt;如果你真的需要。

答案 4 :(得分:3)

我只想指出,使用动态类型转换以便基于类型进行分支通常会暗示架构中的缺陷。大多数时候,您可以使用虚拟功能实现相同的效果:

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];
};

答案 5 :(得分:2)

遗憾的是,在C ++中没有简单的方法。您必须自己创建一个基类,并从该类派生所有其他类。创建一个基类指针向量,然后使用dynamic_cast(它带有自己的运行时开销)来查找实际类型。

答案 6 :(得分:2)

为了完成这个主题,我想提一下,你可以通过使用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;
}

答案 7 :(得分:2)

虽然你不能在容器中存储基本类型,但你可以创建基本类型的包装类,它类似于Java的自动装箱基元类型(在你的例子中,原始类型的文字实际上是自动装箱的);其实例出现在C ++代码中(并且可以(几乎)可以使用)就像原始变量/数据成员一样。

请参阅Object Wrappers for the Built-In Types中的Data Structures and Algorithms with Object-Oriented Design Patterns in C++

使用包装对象,您可以使用c ++ typeid()运算符来比较类型。 我很确定以下比较可行: if (typeid(o) == typeid(Int)) [其中Int将是int基本类型的包装类,等等...] (否则只需向返回typeid的原始包装器添加一个函数,从而: if (o.get_typeid() == typeid(Int)) ...

话虽如此,关于你的例子,这对我有代码味道。 除非这是您检查对象类型的唯一位置, 我倾向于使用多态(特别是如果你有其他类型的方法/函数)。在这种情况下,我将使用原始包装器添加一个接口类,声明延迟方法(用于执行'do stuff'),这将由每个包装的基本类实现。有了这个你就可以使用你的容器迭代器并消除你的if语句(再次,如果你只有这种类型的比较,使用多态来设置延迟方法只是因为这将是过度杀伤)。

答案 8 :(得分:1)

我是一个相当缺乏经验的人,但这就是我要去的地方 -

  1. 为您需要操作的所有类创建基类。
  2. 编写容器类/重用容器类。 (在看到其他答案后修改 - 我之前的观点太神秘了。)
  3. 写相似的代码。
  4. 我相信可以提供更好的解决方案。我也相信可以做出更好的解释。我已经了解到我有一些糟糕的C ++编程习惯,所以我试图在不进入代码的情况下传达我的想法。

    我希望这会有所帮助。

答案 9 :(得分:1)

除了大多数人已经指出的事实,你不能这样做,或者更重要的是,你真的不想这样做。

让我们摒弃你的榜样,并考虑更接近现实生活中的例子。具体来说,我在一个真正的开源项目中看到了一些代码。它试图模拟字符数组中的cpu。因此,它将在数组中放入一个字节“操作码”,后跟0,1或2个字节,可以是字符,整数或指向字符串的指针,基于操作码。为了解决这个问题,它涉及了许多小小的问题。

我的简单解决方案:4个独立的堆栈&lt;&gt; s:一个用于“操作码”枚举,一个用于字符,整数和字符串。取下操作码堆栈的下一个,然后将获取其他三个中的哪一个来获取操作数。

很有可能以类似的方式处理您的实际问题。

答案 10 :(得分:0)

好吧,您可以创建一个基类,然后创建从中继承的类。然后,将它们存储在std :: vector中。

答案 11 :(得分:0)

简短的回答是......你做不到。

答案很长......你必须定义自己的新对象,这些对象都是从基础对象继承的。在Java中,所有对象最终都来自“对象”,这是允许您执行此操作的对象。

答案 12 :(得分:0)

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
   }
}

麦克