Click here to Skip to main content
Click here to Skip to main content

How To Place Complex Class with Dynamic Array in Continuous Memory Block

, 22 Oct 2007 CDDL
Rate this:
Please Sign up or sign in to vote.
Article describes a way to place complex class containing dynamic array with non-trivial members in continuous memory block

Abstract

Often, it is useful to allocate continuous memory blocks for some data to hold a picture or video in it, and such objects have non-trivial structure. For instance, it can be some class with a dynamic array of elements and the static header as for bitmap that we want to send over the network. The problem is that we want to choose an array size dynamically. Moreover, we want to do it in the simplest and fastest way. This article is written to solve that problem.

Introduction

To send some data block via a network or shared memory, it is required to be continuous. It can't contain pointers or classes based on dynamic memory allocation – std::vector, for instance. (To be honest it is possible to send non-continuous data, but it could require writing a lot of code on both client and server side. And that code could be hard to support). So what to do in this case?

The simplest way to solve the problem is to use C++ zero-sized arrays. Let's look at the following sample:

struct DynamicSizeStruct
{
  int a, b;
  float g;
  // Microsoft compiler reports warning here, but it is safe to use
  long SomeData[0]; 
}

Ok, we have a structure declaration now. It's time to allocate some memory. The very first thought is to use malloc or it can be a function called operator new. Anyway, there is no difference at this point.

size_t data_count = 100;
DynamicSizeStruct* p = reinterpret_cast<DynamicSizeStruct*>
    ( malloc( sizeof(DynamicSizeStruct) + sizeof(long)*data_count ) );

Quite a lot of code for simple memory allocation... isn't it? That's the first problem, but it is not the biggest. Let's look at the more complex sample.

class DataUnit
{
public:
  DataUnit();
  ~DataUnit();

  long Data;
};

class DynamicSizeStruct
{
public:
  DynamicSizeStruct();
  virtual ~DynamicSizeStruct();

  int a, b;
  float g;
  DataUnit SomeData[0];
};

Well, we can use malloc as earlier, but the constructor and destructor will not be invoked. We could even use a global function operator new for DynamicSizeStruct, but what about DataUnit? That's the second problem – how to invoke constructors/destructors for all of the array members?

Here we have two problems. Now is the time for solving it.

Reduce Code Size

To solve the first problem, we'll use a C++ template:

template<typename BaseT, DataT> class DynamicArrayHolder : public BaseT
{
public:
  DynamicArrayHolder() {};
  virtual ~DynamicArrayHolder() {};

  void* operator new( std::size_t size, std::size_t count );
  void operator delete( void* p, std::size_t /*count*/ ) 
        { ::operator delete ( p ); };
  const DataT& operator[]( std::size_t pos ) const { return N[pos]; }
  DataT& operator[]( std::size_t pos ) { return N[pos]; }

protected:
  size_t Ncount; // array size
  DataT DAHData[0]; // dynamic array declaration
}

The template has two parameters:

  • BaseT - The base class to which we want to add dynamic size array
  • DataT – Type of data elements in array

I declared operator new here to allocate all necessary memory at once. Operator delete is quite simple, it just releases all allocated memory. Index operator allows to gain access to data array members.

As you can see, the constructor and destructor are trivial at this point. The operator new has to care about allocation memory required by DataT elements. The parameter size will be filled by the C++ compiler at compile time (it will contain a number of bytes required to hold all data members of class BaseT + 4 bytes for a virtual table). We will use the parameter count to define the number of elements at runtime.

void* operator new( size_t size, size_t count )
{
  void* p = ::operator new ( size + sizeof( DataT ) * count );
  reinterpret_cast<DynamicArrayHolder*>( p )->Ncount = count;
  return p;
}

Now we can compare the old way to allocate data...

struct DynamicSizeStruct
{
  int a, b;
  float g;

  // Microsoft compiler reports warning here, but it is safe to use
  long SomeData[0]; 
};

void someFunc()
{
  size_t data_count = 100;
  DynamicSizeStruct* p = reinterpret_cast< DynamicSizeStruct* >
        ( malloc( sizeof( DynamicSizeStruct ) + sizeof(long)*data_count ) );
}

... with the new one:

struct BaseStruct
{
  int a, b;
  float g;
};

typedef DynamicSizeStruct DynamicArrayHolder< BaseStruct, long >;

void someFunc()
{
  size_t data_count = 100;
  DynamicSizeStruct* p = new (data_count) DynamicSizeStruct;
}

Less code and more readable as we planned! In the final implementation that you can download, I've added three more functions GetDataPtr, GetByteSize and GetArraySize. GetDataPtr returns a pointer to the actual data of the BaseT class and you can use that pointer in memcpy functions with the result of GetByteSize function. And finally, GetArraySize returns a count of elements in a dynamic array. I have to notice that there is one known restriction – we cannot construct BaseStruct with non-empty parameters list constructor.

Time to Invoke DataT's Constructors

We've already seen the global function ::operator new in our operator new. But there we used the "most common" form of that function. There is another one. And we will use it to invoke constructors for array members. The new constructor implementation will look like that:

DynamicArrayHolder()
{
  for ( size_t n = 0; n < Ncount; ++n ) new ( &N[n] ) DataT;
};

It just constructs DataT elements one by one on an already allocated memory block. In the destructor, we have to call the destructor for all DataT elements:

virtual ~DynamicArrayHolder()
{
  for ( size_t n = 0; n < Ncount; ++n ) N[n].~DataT();
};

The full version of DynamicArrayHolder template is available in the DynamicArrayHolder.hpp file. Additionally, it contains the copy constructor and some primitive error processing in the code. I've also written sample code that has been placed in the main.cpp file.

History

  • 22nd October, 2007: Initial post

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)

Share

About the Author

Kirill V. Lyadvinsky
Team Leader
Russian Federation Russian Federation
C++ Developer. In 2002 developed the first automatic eBay bidder (Last Minute Bidder) for Paragon Software GmbH.
Now - Head of R&D laboratory in ElVEES R&D center of Microelectronics.

Comments and Discussions

 
GeneralLooks pretty good to me & Suggestions... PinmemberIain Clarke23-Oct-07 1:37 
AnswerRe: Looks pretty good to me &amp; Suggestions... PinmemberKirill V. Lyadvinsky29-Oct-07 23:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411022.1 | Last Updated 22 Oct 2007
Article Copyright 2007 by Kirill V. Lyadvinsky
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid