【问题标题】:Pointer to array Maintain counter of elements指向数组的指针维护元素的计数器
【发布时间】:2015-01-17 05:59:09
【问题描述】:

我有一个多个类继承的接口。

    class someInterface
    {
        virtual void someMethod() = 0;
    }
    class A : public someInterface
    {
        public:
            void someMethod()
            {
                //Do something
            }
    }

    class B : public someInterface
    {
        public:
            void someMethod()
            {
                //Do something
            }
    }

    class C : public someInterface
    {
        public:
            void someMethod()
            {
                //Do something
            }
    }

对于每个类 A、B、C,我在容器类中创建了一个具有不同大小的实际类型的数组。

    class AContainer 
    {
        public:
            A As[10];
    }

    class BContainer 
    {
        public:
            B Bs[5];
    }
    etc...

此外,我有一个指向“SomeInterface”的指针数组,我想在其中有一个指向每个像这样的实际数组的指针。

    #define SOMEINTERRFACE_SIZE 3
    someInterface *array[SOMEINTERRFACE_SIZE];
    array[0] = AContainer.As; //Could also just be &AContainer.As[0]
    array[1] = BContainer.Bs;
    array[2] = CContainer.Cs;

    for (int i = 0; i < SOMEINTERRFACE_SIZE; ++i)
    {
        int elements = //Here i need a solution to get the size
                       //So i can iterate through the array, which the pointer points to.
        for (int i = 0; i < elements; ++i)
        {
            //Call the interface method on each element.
        }
    }

当我必须使用 someInterface 数组时,问题就出现了,因为无法通过 someInterface 指针获取实际数组的大小..

什么是解决这个问题的好方法?我真的需要一些帮助来解决这个问题。 也不想使用动态分配,所以没有使用 vector 或 malloc 等的解决方案,因为我正在写一个 Arduino。

【问题讨论】:

  • Arduino C++ 没有异常。
  • @KarolyHorvath:你为什么不尝试编译这段代码(至少第一部分)? SO 的一个好习惯是提供可编译的示例或至少提供有效的代码。

标签: c++ static-allocation


【解决方案1】:

这行不通。在 C++ 中,您必须知道数组中元素的大小。 ABC 的大小可能不同,因此您不能将它们的数组视为相同。

&AContainer.As[i] == &AContainer.As + i * sizeof(A)

但是

&BContainer.Bs[i] == &BContainer.Bs + i * sizeof(B)

因此,相同的机器代码不可能遍历 AB 的数组。如果你想遍历一个对象数组,你需要知道确切的类型。

请记住,在 C++ 中,如果要获得多态虚拟调用,则需要通过指针或引用。解决方案是将每个数组中元素的指针复制到一个“主”数组中。

SomeInterface *ptrs[NUM_A + NUM_B + NUM_C];
SomeInterface **dest = ptrs;
for (int i = 0; i < NUM_A; ++i) {
    *dest++ = &AContainer.As[i];
}
for (int i = 0; i < NUM_B; ++i) {
    *dest++ = &BContainer.Bs[i];
}
// et cetera...

这仅使用了一点额外空间,因为您存储的是指针,而不是实际的对象。

编辑:如果你真的想节省空间,我想你可以这样做:

someInterface *arrays[] = { AContainer.As, BContainer.Bs, CContainer.Cs };
int objSizes[] = { sizeof(A), sizeof(B), sizeof(C) };
int arrLengths[] = { NUM_A, NUM_B, NUM_C };

for (int j = 0; j < sizeof(arrays)/sizeof(arrays[0]); ++j)
{
    void *ptr = arrays[j];
    for (int i = 0; i < arrLengths[j]; ++i) {
        someInterface *iptr = (someInterface *)ptr;
        iptr->method();
        ptr += objSizes[j];
    }
}

(这是未经测试的,您可能需要稍作调整。)

理论上,由于所有这些数组都充满了编译时常量,它应该快速优化。如果不这样做,代码将运行得更慢,因为它将通过仅在运行时而不是编译时知道的值递增指针。如果你真的关心速度,你应该检查汇编输出。

【讨论】:

  • 我应该写下这是我当前的实现,我只是认为有某种巧妙的方法可以通过仅存储指向实际数组的第一个元素的指针来节省空间。还是谢谢你:)
  • @ValentinHeintz 您知道代码需要在具有 2-4kb RAM 的微控制器上编译吗?再次标记了您的评论。
  • @Peter:对不起,但是你应该将你的问题标记为“arduino C++”或“C++ limited”或你的编译器的名称
  • 我在描述中写道,我正在为 Arduino 编程。下次我会使用 Arduino c++ 标签。
【解决方案2】:

这是您想要达到的目标吗? 遍历对象列表并调用公共接口的重新实现方法?

将这段代码放在全局 C++ 范围内的任何位置进行测试。

#include <vector>
#include <iostream>
int TestSomewhereInCppGlobalScopeCode()
{
    class someInterface
    {
        public:
            virtual void someMethod() = 0;
    };

    class A : public someInterface
    {
        public:
            void someMethod()
            {
                std::cout << "A::someMethod()";
            }
    };

    class B : public someInterface
    {
        public:
            void someMethod()
            {
                std::cout << "B::someMethod()";
            }
    };

    class C : public someInterface
    {
        public:
            void someMethod()
            {
                std::cout << "C::someMethod()";
            }
    };

    std::vector<someInterface*> ListOfObjectsHavingCommonInterface;

    ListOfObjectsHavingCommonInterface.push_back( new A );
    ListOfObjectsHavingCommonInterface.push_back( new B );
    ListOfObjectsHavingCommonInterface.push_back( new C );

    for ( std::vector<someInterface*>::iterator it = ListOfObjectsHavingCommonInterface.begin();
          it != ListOfObjectsHavingCommonInterface.end();
          ++it )
    {
        (*it)->someMethod();
    }

    return 0;
}

static int TestSomewhereInCppGlobalScopeCode_Caller = TestSomewhereInCppGlobalScopeCode();

【讨论】:

  • 不,抱歉,无权访问矢量类。
【解决方案3】:

如果不了解您的应用程序的更多详细信息,很难回答 - 但这里有一些想法可能会有所帮助。

给定:

class someInterface { public: virtual char someMethod() = 0; };

class A : public someInterface { public: char someMethod() { return 'A'; } };
class B : public someInterface { public: char someMethod() { return 'B'; } };
class C : public someInterface { public: char someMethod() { return 'C'; } };

你可以像这样手动滚动:

class Array {
public:
    void forEach( void(*function)(someInterface&) ) {
        for (size_t i = 0  ;  i < countA  ;  ++i) function(As[i]);
        for (size_t i = 0  ;  i < countB  ;  ++i) function(Bs[i]);
        for (size_t i = 0  ;  i < countC  ;  ++i) function(Cs[i]);
    }
private:
    enum {countA = 10, countB = 5, countC = 3};
    A As[countA];
    B Bs[countB];
    C Cs[countC];
};

void doSomeMethod(someInterface& element) {
    std::cout << element.someMethod();
}

int main(int, char**) {
    Array array;
    array.forEach(doSomeMethod);
    return 0;
}

请注意,通过使用“回调”函数doSomeMethod,我们解决了多态集合中调度的典型问题。 当然,您不想继续手动滚动这样的事情。幸运的是,我签出的 Arduino C++ 编译器支持模板,因此您可以执行以下操作:

template <class T, size_t _size, class NextArray = void>
struct Array {
public:
    typedef T value_type;
    enum {size = _size};

    void forEach( void(*function)(someInterface&) ) {
        for (size_t i = 0  ;  i < _size  ;  ++i)
            function(elements[i]);
        nextArray.forEach(function);
    }
private:
    T elements[_size];
    NextArray nextArray;
};

template <class T, size_t _size>
struct Array<T, _size, void> {
public:
    typedef T value_type;
    enum {size = _size};

    void forEach( void(*function)(someInterface&) ) {
        for (size_t i = 0  ;  i < _size  ;  ++i)
            function(elements[i]);
    }
private:
    T elements[_size];
};

void doSomeMethod(someInterface& element) {
    std::cout << element.someMethod();
}

int main(int, char**) {
    Array<A, 10, Array<B, 5, Array<C, 3> > > array;
    array.forEach(doSomeMethod);
    return 0;
}

这让编译器为您编写不同类型和大小的组合。有几点值得注意:

  1. 所有的魔法都在编译时完成。查看优化编译器生成的程序集,看看它有多小和多快。
  2. 如果您的回调函数需要一些状态,请阅读 c++“函子”。
  3. 如果您的编译器支持可变参数模板和/或 lambda,这会变得更简单(我假设 Arduido 编译器还不支持)

如果您不能使用回调方法(并且您的编译器还不支持 lambda),那么您可以尝试下一个选项,它会比上面给出的选项产生一些小的运行时成本:

template <class Interface>
class ArrayInterface {
public:
    virtual size_t getSize() = 0;
    virtual Interface& getElement(size_t index) = 0;
};

template <class T, class Interface, size_t size>
class Array : public ArrayInterface<Interface> {
public:
    size_t getSize() { return size; }
    Interface& getElement(size_t index) { return element[index]; }
private:
    T element[size];
};

int main(int, char**) {
    Array<A, SomeInterface, 10> As;
    Array<B, SomeInterface, 5> Bs;
    Array<C, SomeInterface, 3> Cs;

    const int SOMEINTERRFACE_SIZE = 3;
    ArrayInterface<SomeInterface>* array[SOMEINTERRFACE_SIZE] = {&As, &Bs, &Cs};

    for (size_t i = 0  ;  i < SOMEINTERRFACE_SIZE  ;  ++i) {
        ArrayInterface<SomeInterface>& innerArray = *array[i];
        for (size_t j = 0  ;  j < innerArray.getSize()  ;  ++j)
            std::cout << innerArray.getElement(j).someMethod();
    }

    return 0;
}

(根据您的问题,最后一个使用指针的外部数组)

这篇文章已经太长了,所以我没有详细介绍,也没有深入研究诸如成员函数指针的单个平面数组之类的选项。如果您有任何问题,请大声疾呼。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-11
    • 1970-01-01
    相关资源
    最近更新 更多