【问题标题】:Is there a better way to encapsulate a shared storage pool that member objects can access?有没有更好的方法来封装成员对象可以访问的共享存储池?
【发布时间】:2019-09-11 10:36:04
【问题描述】:

总体目标

我正在为没有动态内存分配的嵌入式系统编写一个存储库,它可以处理在单个数组/静态内存池中存储几种不同类型的对象。对象应该表现得好像它们是彼此的成员一样。

对象的数量未知。数据来自结构化源,因此只需将其添加到存储中一次。数据按顺序添加,但需要任意读取。

具体问题

每个对象都是一个简单的类,但这些类的实例需要访问共享存储池。

我的解决方案

我设置了一个包含简单 POD 对象的联合数组,所有这些对象都具有相似的大小。这些对象将其孩子在池中的位置存储为私有成员。这两种不同的类型从相反的方向生长,因此只有对象的总数是有限的。

添加到商店可以通过主类完成,主类需要访问这些私有成员,因此主类在对象类中被声明为友元。

但是,对象不能访问主存储池,所以现在我已经在类之外声明了存储池。由于开销,我不想在对象中存储指向存储的指针。一次只需要存在一家商店。

代码(用于 C++11)

以下是工作代码,但它不会阻止访问存储池。那么有没有更好的办法呢?


#include <cstdio>
#include <cstdint>

namespace Store{

const int STORE_SIZE = 512; 

// 8 bytes
class Chair {
public:
    int a;
    int n_legs;
};

// 8 bytes
class Room {
    friend class Flat; 

private:
    uint16_t chairs_start;

public:
    uint16_t number_of_chairs;
    int a;
    int b;

    // member functions    
    Chair getChair( int idx );
};

// 8 byte element of storage can be either one or the other
union Element{
    Chair chair;
    Room room;
};

union Element;
Element pool[STORE_SIZE];

Chair Room::getChair( int idx )
{
    return pool[chairs_start - idx].chair; // needs access to pool!
}

class Flat{
public:

    void addRoom() {
        Room room;
        room.chairs_start = pool_next_chair;
        pool[pool_next_room++].room = room;
    }

    void addChair() {
        Chair chair;
        chair.a = pool_next_chair; // only for testing

        pool[pool_next_chair--].chair = chair; // add chair to pool from other side

        // incremente chair count for last added room
        pool[pool_next_room - 1].room.number_of_chairs++; 
    }


    Room getRoom( int idx ){
        return pool[idx].room;
    }

private:

    uint16_t pool_next_room = 0;    
    const uint16_t pool_first_chair = STORE_SIZE - 1;   
    uint16_t pool_next_chair = pool_first_chair;    
};

}

int main() {

    Store::Flat flat;

    flat.addRoom();
    flat.addChair();
    flat.addChair();
    flat.addRoom();
    flat.addChair();

    auto room2 =  flat.getRoom(1);

    auto chair1 = room2.getChair(0);

    printf("Got chairs %d and %d\n",chair1.a, flat.getRoom(0).getChair(1).a);
    // Got chairs 509 and 510
    return 0;
}

代码有效,但池可访问。一般来说,我可以使用更好的布局或结构来实现与上述 main() 函数类似的简单访问方式吗?

非常欢迎任何其他代码质量的 cmets!非常感谢!

【问题讨论】:

    标签: c++


    【解决方案1】:

    我不确定这是您要求的,但是...您希望 pool 对象只实例化一次并且只能由特定类访问,对吧?

    如果我正确理解了您的问题,我认为您应该将pool 对象声明为privateprotected static 类的成员,并将需要访问它的类声明为friend s.
    这样您就可以保证它只会被创建一次,并且只有允许的类可以访问它。

    如果我误解了您的问题,请告诉我。


    顺便说一句:Chair 包含 2 个ints,大小为 8(字节)。你怎么能假装包含 3 个 ints 和 2 个 uint16_tRoom 可以具有相同的大小?
    Room 的大小至少为 16 个字节。实际上,通过内存对齐,它的大小可能会改为 20。
    如果将chairs_start 声明移到number_of_chairs 之后,您将得到预期的16 大小。


    编辑:这是我的意思的一个例子。

    #include <array>
    namespace test
    {
        class Thing
        {
            // Fill the definition as you wish
        };
        class OtherThing
        {
            // Fill the definition as you wish
        };
    
        union Element
        {
            Thing t;
            OtherThing ot;
        };
    
        class Pool
        {
            friend class Thing;                               // Thing can access the private Pool::pool
            friend class OtherThing;                          // OtherThing can access the private Pool::pool
    
            public:
                static const size_t STORE_SIZE = 512;
    
            private:
                static std::array <Element, STORE_SIZE> pool; // The static pool will be instantiated only once
        };
    }
    

    不要忘记在 .cpp 文件中的某处初始化pool。这可能如下所示(例如):

    std::array<test::Element, test::Pool::STORE_SIZE> test::Pool::pool {}; // static data member initialization
    

    【讨论】:

    • 好的,所以 Flat 都应该是 Room 的朋友,反之亦然?班应该是彼此的朋友?我会试试的。感谢您注意到第三个 int 和对齐问题,我已经在我认为的示例代码中修复了它。
    • @MarkUcka 不完全是,我的意思是例如 class Pool 包含一个 private static 池数据成员。并且您声明 ChairRoom 以及需要访问池数据成员的任何内容作为 Pool 类的 friends。
    • @MarkUcka 我添加了一个例子,如果它可以帮助你。
    【解决方案2】:

    如果你愿意,你可以从另一个角度解决问题。

    首先,做一个动态存储功能。这可能只是实现您自己的,也许是原始的分配器(这基本上意味着实现您自己的和allocate/deallocate,请参阅https://en.cppreference.com/w/cpp/memory/allocator)。

    然后你可以将你的分配器用于任何类型的集合,例如std::vector

    #include <vector>
    
    template <class T>
    using myvector = std::vector<T, MyAllocator<T> >;
    myvector<Room> rooms;
    

    除了更通用的明显优势之外,这还有一个额外的好处,即当对象小于联合大小时不会使用额外的空间。


    更新:这是一个使用“竞技场风格”分配器让您入门的示例。这与 OP 提供的内容相当,即。没有错误检查(没有缓冲区溢出检查),也没有重复使用缓冲区。

    #include <vector>
    
    char mem[512*10];
    unsigned int current = 0;
    
    template<class T>
    struct my_allocator {
      using value_type = T;
      T* allocate( std::size_t n ){current += n; return mem + current - n;}
      void deallocate( T* p, std::size_t n ){}
    
    };
    
    std::vector<int, my_allocator<int> > rooms(my_allocator<int>{});
    

    链接到godbolt

    【讨论】:

    • 我想房间类将包含椅子的成员向量。听起来很有趣,但我现在会坚持(对我来说)更容易理解的数组方法。
    • @MarkUcka 向量就是这样,一个(动态)数组
    • @MarkUcka 请看我的更新,你认为你可以使用它吗?
    • 是的,我认为一般来说这将是一个好方法,但我的代码将在没有异常处理的项目中使用,所以我不认为这是要走的路。 stackoverflow.com/a/9612884/12021380 也建议使用 EASTL。
    • @MarkUcka 用于错误处理,您不必抛出异常,例如,您可以终止程序。
    【解决方案3】:

    您的要求I don't want to store a pointer in the objects to the store because of the overhead. 已经在很大程度上推动了设计。您有以下选择:

    1. 使用全球商店(就像您一样)。
    2. 将 store in 参数传递给 add/del 函数
    3. 上述的混合(使用单例全局存储作为默认参数)

    在所有情况下,您都应该将您的商店封装在一个类中,并有一个通用的分配方案。在当前的设计中,你不能有超过 2 个元素(因为你是从末端分配它们),并且 add 是在对象的函数中实现的。

    由于您使用池中的位置来添加元素,因此您可以在池中使用单个递增索引并假设椅子分配在它旁边。

    例子:

    class Store
    {
    public:
        static Store& globalStore(){static Store g; return g;}
    public:
        Room& allocateRoom() {return store[next_available_elt++].room;}
        Chair& allocateChair() {return store[next_available_elt++].chair;}
    
        Chair& getChair(Room& room, size_t i)
        {
            size_t index_room = std::distance(&store[0],(Element*)&room);
            return store[index_room + i];
        }
    
    private:
        union Element{
            Chair chair;
            Room room;
        };
        Element store[STORE_SIZE] = // init with chaining of next;
        size_t next_available_elt = 0;
    };
    

    请注意,这种设计很脆弱。您最好在椅子/房间中添加链接的开销,它的设计更简洁,但代价是 O(N) 访问椅子。

    【讨论】:

    • 通过这个示例,您如何访问房间?例如,函数如何获得 room(4)?
    • 是的。您将拥有 O(n) 访问权限而不是随机访问权限
    猜你喜欢
    • 2018-01-30
    • 1970-01-01
    • 2012-11-14
    • 1970-01-01
    • 2014-10-23
    • 2011-05-26
    • 1970-01-01
    • 1970-01-01
    • 2017-11-23
    相关资源
    最近更新 更多