【问题标题】:C++: treating structure members as an arrayC ++:将结构成员视为数组
【发布时间】:2012-07-08 18:22:28
【问题描述】:

我有一个包含很多(比如数百个)指针的结构。每个指针都是不同的类型,但它们都继承自一个公共基类 --- 我们称它为 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* 用于调用方法。当然,这很糟糕,因为它会使数据结构的大小增加一倍。

因此,如果有人想确认这一点(我很想被证明是错误的),我很乐意将他们的答案标记为正确...

【问题讨论】:

  • 您是否能够将所有指针都设为基指针,或者您有时必须调用仅为某些派生类型定义的方法?
  • 后者。每个成员都需要是适当的类型。

标签: c++ pointers types rtti


【解决方案1】:

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

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

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

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

并将它们转换为 Base* 可能涉及更改它们

并非如此,对于public 继承,强制转换应该是隐式的。

如果你只对它们都调用Foo()Foo() 可以是基类中的virtual 方法,这样你就可以充分利用多态性。

【讨论】:

  • 他说这个类是机器生成的,所以也许他不能制作向量?
  • @loganfsmyth 我错过了那部分......但如果它是机器生成的,我看不出问题的重点。我的意思是......代码就是这样。
  • 嗯,是的,但如果我不需要每个成员都是不同的类型,我就不会让他们成为不同的类型!只有这一段代码需要综合处理它们。实际上,我正在考虑将每个指针存储两次:一次作为用于大量代码的真实类型,一次作为用于聚合的 Base。但这会使数据结构的大小增加一倍,并且需要将每个指针写入两次,所以我宁愿避免这样做。
【解决方案2】:

但是,当然,我不能在 C++ 中这样做,因为结构成员 不是统一类型,将它们转换为 Base* 可能涉及更改 它们,这可能涉及更改指针,因为它们是 不是统一类型的指针将不得不以不同的方式更改 对于每个成员,我不能这样做。

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

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

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

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

【讨论】:

  • 这仅适用于线性继承情况,您知道指向同一对象的不同类型的指针具有相同的值。当像我一样使用多重继承时,这是不正确的 --- 当将 Class1* 转换为 Base* 时,编译器会插入指令来更改指针。
【解决方案3】:

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

您还可以尝试按照以下建议将您的数据转换为元组,可能重复,问题:Iterate through struct variables

我不确定哪个更快...

如果您可以修改自动生成此源代码的工具 - 也许最好是扩展此工具...

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

      我不想更改我之前的答案。但是,我提出了另一个适合您的解决方案:此处的完整示例: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);
      }
      

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

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

      【讨论】:

        【解决方案6】:

        所有指针的大小都相同(C++ 中的所有类对象指针大小相同),实际上编译器需要相当反常才能在此指针列表中的任何位置插入填充。无论如何,可能填充的问题与C中相同。因此您可以像C中一样做,完全没有问题。

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

        就这么简单。

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

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

        【讨论】:

        • 不行,恐怕——多重继承意味着很多关于指针的C假设都是无效的。
        • 我先写了一条评论说你错了,但后来我意识到你总是可以使它不起作用。这与让它发挥作用相反。我很愚蠢地回答你的问题,对不起。
        猜你喜欢
        • 2016-12-04
        • 1970-01-01
        • 1970-01-01
        • 2021-05-19
        • 2022-01-02
        • 2017-06-11
        • 2011-01-17
        • 2011-04-11
        • 2011-03-04
        相关资源
        最近更新 更多