C++:将结构成员视为数组

C++: treating structure members as an array

本文关键字:数组 成员 结构 C++      更新时间:2023-10-16

我有一个包含许多(比如数百个)指针的结构。每个指针都是不同的类型,但它们都继承自一个公共基类——让我们称之为base。我正在使用多重继承。(这都是机器生成的,这就是为什么它很奇怪。)

例如:

class Data {
    Class1* p1;
    Class2* p2;
    Class3* p3;
    ...etc...
};

我想调用在Base中基于所有这些定义的方法。我可以生成这样的代码:

void callFooOnData(Data* p)
{
    if (p1) p1->Foo();
    if (p2) p2->Foo();
    if (p3) p3->Foo();
    ...etc...
}

问题是,我有很多很多,有十亿种不同类型的数据,上面的代码非常大,影响了我的足迹。所以我试着用更聪明的东西来代替它。

在C中,我可以简单地获取第一个结构成员的地址和计数,并执行以下操作:

void callFooOnData(Data* p)
{
    callFooOnObjects(&p1, 3);
}

但是,当然,我不能在C++中做到这一点,因为结构成员不是统一的类型,将它们强制转换为Base*可能需要更改它们,这可能需要更改指针,因为它们不是统一类型,所以每个成员的指针都必须以不同的方式更改,我不能这样做。

有没有一种方法可以用C++来做这样的事情?这都是机器生成的代码,所以它不一定很漂亮,谢天谢地——鲨鱼已经被跳过了——但我确实需要它尽可能便携。。。

(我确实可以访问RTTI,但出于性能原因,如果可能的话,我想避免它。)

更新:

所以,据我所知。。。你在C++中无法做到这一点。根本做不到。我被一个事实误导了,那就是它在C中是完全直接的。在C++中,只有当你有一个类型良好的指针开始时,你才能在类层次结构中的两个类型之间安全地投射一个指针,当然,我没有。

因此,我必须改变潜在的问题来避免这种情况:我提出的解决方案是将每个指针存储在结构中两次,一次存储在Base*数组中用于迭代,另一次存储为ClassWhatever*用于调用方法。当然,这很糟糕,因为它会使数据结构的大小翻倍。

因此,如果有人想证实这一点(我希望被证明是错的),我会很高兴地将他们的答案标记为正确的。。。

每个指针都是不同的类型,但它们都继承自一个公共基类——让我们称之为Base

与其拥有数百个成员,不如保留一个Base类指针的容器:

class Data {
   std::vector<Base*> objects;
};

在一个好的设计中,你不需要知道每个对象的类型,如果把它抽象掉会更好。记住,针对接口而不是具体类进行编程总是很好的。

将它们铸造到基地*可能需要将其更改为

不是这样,对于public继承,强制转换应该是隐式的。

如果只对所有方法调用Foo(),那么Foo()可以是基类中的virtual方法,这样就可以充分利用多态性。

但是,当然,我不能在C++中做到这一点,因为结构成员不是统一的类型,将它们铸造到Base*可能需要更改它们,这可能涉及到更改指针,并且因为它们不是一个统一的类型,指针必须以不同的方式更改每个成员,我不能那样做。

尽管这是个坏主意,但事实并非如此:您可以像在C中那样使用指针算术来迭代指针。指针大小都相同,并且它们有一个公共基类(不考虑结构对齐问题等)。它很脏,但从技术上讲它是有效的。如果您将实例本身作为类中的成员而不是指向它们的指针,则情况会有所不同。

C++11中最好的方法是使用

std::vector< std::unique_ptr<Base> > objects;

参见http://www.drdobbs.com/cpp/c11-uniqueptr/240002708

您必须通过自动生成代码来"对抗"自动生成的代码。您可以使用ctags之类的工具来"解析"自动生成的类,并从ctags输出中自动生成代码。看见http://ctags.sourceforge.net/ctags.html.

您也可以尝试将数据强制转换为元组,如以下可能重复的问题所建议的:遍历结构变量。

我不确定哪个更快。。。

如果你能修改自动生成这个源代码的工具,也许最好的办法是扩展这个工具。。。

另一个选项(不可编译):

class DataManipulator 
{
public:
   DataManipulator(const Data& aData_in)
   {
      objects.add(aData_in->Class1);
        .
        .
   }
   void operate()
   {
       for(objects_iterator...)
       {
           if(*objects_iterator != NULL)
                 objects_iterator->Foo();
       }
   }
private:
   std::vector<Base*> objects;
};

我不想更改以前的答案。然而,我找到了另一个应该对您有效的解决方案:这里的完整示例:http://ideone.com/u22FO

以下主要部分:

struct C {
  A1* p1;
  A2* p2;
  A3* p3;
  A4* p4;
  // ...
};
template <class D, size_t  startNumber, size_t numMembers, bool notFinished>
struct FooCaller;
template <class D, size_t  startNumber>
struct FooSingleCall;
template <class D, size_t  startNumber, size_t numMembers>
struct FooCaller<D, startNumber, numMembers, false> {
   void operator() (D& d) {}
};
template <class D, size_t  startNumber, size_t numMembers>
struct FooCaller<D, startNumber, numMembers, true> {
   void operator() (D& d) {
       FooSingleCall<D,startNumber>()(d);
       FooCaller<D, startNumber + 1, numMembers, startNumber < numMembers>()(d);
   }
};
#define FooSingleCallD(n) 
template <class D> 
struct FooSingleCall<D,n>{ 
   void operator() (D& d) { 
       d.p##n->foo(); 
   } 
}
FooSingleCallD(1);
FooSingleCallD(2);
FooSingleCallD(3);
FooSingleCallD(4);
// ... unfortunately repeat as many times as needed
template <class D, size_t numMembers>
void callFoo(D& d)
{
   FooCaller<D, 1, numMembers, 1 <= numMembers>()(d);
}

Aware:足够聪明,不要定义数百个FooSingleCall。。。看看这个著名问题的答案之一:https://stackoverflow.com/a/4581720/1463922您将看到如何在5行中创建1000个实例。。。

此外,请将FooSingleCall替换为从类中检索N指针的东西——比如GetNPointer。。。

所有指针大小相同(C++中的所有类对象指针大小相同),在实践中,编译器需要非常反常才能在指针列表中的任何位置插入填充。无论如何,可能的填充问题和C中的一样。所以你可以做和C中一样的事情,一点问题都没有。

void foo( Base const* );
void bar()
{
    // ...
    foo( *(&thingy.p1 + 3) );
}

就这么简单。

也就是说,即使使用机器生成的代码,设计听起来也很可怕、错误、非常糟糕。

在生成vtables(每个指针指向一个签名通常不同的函数)时,确实有这种情况,但这种情况非常罕见。这听起来像是一个XY问题。比如,您;你试图解决问题X,提出了一个不好的解决方案Y,现在问的是想象中的解决方案,而不是真正的问题X…