繁体   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