Normal C pattern, but expressed in C ++?

A common pattern in C programming includes a variable-length structure, such as:

typedef struct { int length; char data[1]; } MyBuffer; 

where the data is not literally an array from [1]. Instead, its variable length is determined by length .

The structure is distributed as follows:

 MyBuffer* pBuff = malloc(sizeof(MyBuffer) + 100); 

I want to use the same template, but in C ++ code, so use new / delete instead of malloc / free

Is it possible to use the same template in C ++ code? How?

EDIT . Since several answers and comments suggest that I switch to std::vector :

I am provided with a definition of the MyBuffer structure from a third-party C library.
In my C ++ application, I need to allocate a buffer and call a function in the C library.

On the β€œmy side” of the border, I prefer to keep things C ++ and highlight this C ++ structure - the way, but I still need to pass it to the C-library, which does not understand anything like std::vector .

+4
source share
7 answers

If you need to maintain compatibility with the existing C-code that you use, then it works with C ++, almost without changes (you just need to return the return from malloc() ).

 #include <stdlib.h> typedef struct { int length; char data[1]; } MyBuffer; void f() { MyBuffer* pBuff = (MyBuffer *)malloc(sizeof(MyBuffer) + 100); } 

This compiles without problems using g ++.

If you are concerned about managing the allocated memory of my malloc() , then you can create a class to manage it and display the pointer MyBuffer using a member method, for example:

 std::shared_ptr<MyBuffer> buf((MyBuffer *)malloc(sizeof(MyBuffer) + 100), free); 

It's rather cumbersome, I agree ...

+6
source

This is not idiomatic in C ++, and not necessary. The language provides std::vector<unsigned char> , which wraps the size and buffer for you, or if you do not need dynamic resizing at runtime, C ++ 11 provides std::array<unsigned char> .

EDIT: The key to be noted here is not allocating vector by itself in a heap! If you put a vector value on the stack or inside another object and configure it correctly when building, you will use exactly the same amount of allocations as the C version (one). BUT you will use the idiomatic language function And do not allow yourself to make many memory errors and / or leaks.

+5
source

I think this will do the trick, give or take a couple of syntax errors.

 class MyBuffer { // Important, this class must not have any virtual methods. public: void* operator new(size_t data_length) { MyBuffer* buffer = static_cast<MyBuffer>(new char[sizeof(MyBuffer) + data_length]); buffer->length = data_length; return buffer; } private: int length; char data[1]; }; 

Edit:

One of the main drawbacks of this method is that for debugging builds, it is quite common to override the global operator new, providing verification of execution during buffer overflows and memory leaks. I'm not sure how well this will interact with non-standard implementations of the new global operator.

+3
source

You can use the template in C ++, there is no flaw in C. The template parameter determines the size of the array.

 template <unsigned N> struct MyBufferTemplate { int length; char data[N]; }; 

In the case when N not known at compile time, you can define a certain set of values ​​of a reasonable size and perform an optimal distribution.

However, if this seems too wasteful for memory, then another approach is to define an interface over std::vector<int> (this is based on MadScienceDreams comment).

 struct MyBufferAdapter { MyBufferAdapter (int databytes = 1) : buf_(1 + (databytes+sizeof(int))/sizeof(int)) { buf_[0] = databytes; } void resize (int newdatabytes) { int newlength = 1 + (newdatabytes+sizeof(int))/sizeof(int); buf_.resize(newlength); buf_[0] = newdatabytes; } int & length () { return buf_[0]; } int length () const { return buf_[0]; } char * data () { return static_cast<char *>(&buf_[1]); } const char * data () const { return static_cast<const char *>(&buf_[1]; } operator MyBuffer * () { return reinterpret_cast<MyBuffer *>(&buf_[0]); } operator const MyBuffer * () const { return reinterpret_cast<const MyBuffer *>(&buf_[0]); } private: std::vector<int> buf_; }; 
+2
source

Personally, I would go with malloc and free . However, you can use all new[] , the placement of new and delete[] :

 #include <new> struct MyBuffer { int length; char data[1]; }; MyBuffer* make_a_buffer(int size) { // allocate buffer large enough for what we want char* raw_memory = new char[sizeof(MyBuffer) + size]; // call placement new to put a MyBuffer in the raw memory MyBuffer* buffer = new (raw_memory) MyBuffer; buffer->length = size; return buffer; } void destroy_a_buffer(MyBuffer* buffer) { // in this case, MyBuffer has a trivial (default) destructor, so this isn't // really needed, but in other cases you may need to call the // destructor // // NOTE: there is placement new, but no placement delete // this is the only way to correctly destroy the object buffer->~MyBuffer(); // we've destroyed the object, and now we need to release the // memory, luckily we know we got it from new[], so we can // delete[] it delete[] static_cast<char*>(static_cast<void*>(buffer)); } 
+1
source

If you want to use the C structure, but want to use a more efficient approach in C ++, you can use a combination of patterns and inheritance:

 #include <iostream> #include <memory> #include <stdlib.h> // Here your C struct. // Old C-style usage would be: MyBuffer* pBuff = malloc(sizeof(MyBuffer) + 100); // Which effectively gives you a 101-byte array for 'data'. // (1 for the original array, +100). typedef struct { int length; char data[1]; } MyBuffer; // This defines a generic template that inherits from whatever you want, and // adds some padded space to the end of it. The 'extra_bytes' is equivalent // to the '+100' you used to do in the c-style malloc trick (ie this still // allocates a 101-byte array for 'data'). template<typename T, size_t extra_bytes> struct ExtraBytes : public T { char padding[extra_bytes]; }; // If you just want to wrap your one struct, you can use this. The constructor // even sets the length for you. template<size_t array_size> struct MyBufferWrapper : public MyBuffer { char padding[array_size - 1]; // 1 is already allocated to 'data' MyBufferWrapper() { length = array_size; } }; int main(int, char**) { MyBuffer normal; std::cout << "Sizeof normal MyBuffer = " << sizeof(normal) << "\tlength = " << normal.length << "\n"; // length is uninitialized MyBuffer* pBuff = static_cast<MyBuffer*>(malloc(sizeof(MyBuffer) + 100)); std::cout << "Sizeof malloc'd MyBuffer = " << sizeof(*pBuff) << "\tlength = " << pBuff->length << "\n"; // length is uninitialized ExtraBytes<MyBuffer, 100> extra_bytes; std::cout << "Sizeof templated ExtraBytes = " << sizeof(extra_bytes) << "\tlength = " << extra_bytes.length << "\n"; // length is uninitialized MyBufferWrapper<100> wrapper; std::cout << "Sizeof Wrapped MyBuffer = " << sizeof(wrapper) << "\tlength = " << wrapper.length << "\n"; // length is set to 100 // If you reall auto heap = std::make_shared<MyBufferWrapper<100>>(); auto heap = std::make_shared<MyBufferWrapper<100>>(); std::cout << "Sizeof heap-allocated Wrapper = " << sizeof(*heap) << "\tlength = " << heap->length << "\n"; // length is 100 return 0; } 

Note that with this approach you do not need to use malloc/free and new/delete . You simply declare your MyBufferWrapper with any necessary array, it gets allocated on the stack, and you use it (you can think of it like a regular MyBuffer ). If you want to use the allocated memory heap, you can simply use std::unique_ptr or std::shared_ptr .

+1
source

One of C ++ "ish" methods to solve this problem is to describe the buffer itself as "trivially copied" (C ++ 11 lingo, was a "POD" for "plain old data" in C ++ 98 and 2003) struct, with the microscopic exception that he has a private contructor to prevent instantiation. Then create a pointer object for this structure. Here's a complete but trivial program with this idea:

 #include <cstdlib> #include <cstring> struct MyBuffer { int length; char data[1]; private: MyBuffer() {} MyBuffer& operator =(MyBuffer& other) { return other; } }; class MyBufferPointer { MyBuffer *bufptr_; static std::size_t getsize(std::size_t array_size) { return sizeof (MyBuffer) + array_size * sizeof (char); } static MyBuffer *getbuf(std::size_t array_length) { std::size_t sz = getsize(array_length); return static_cast<MyBuffer*>( malloc(sz) ); } public: MyBufferPointer() { bufptr_ = NULL; } MyBufferPointer(std::size_t array_length) { bufptr_ = getbuf(array_length); bufptr_->length = array_length; } MyBufferPointer(const MyBufferPointer &other) { const MyBuffer *op = other.bufptr_; if (op == NULL) { bufptr_ = NULL; } else { bufptr_ = getbuf(op->length); bufptr_->length = op->length; std::size_t sz = op->length * sizeof op->data[0]; std::memmove( bufptr_->data, op->data, sz ); } } MyBufferPointer& operator =(const MyBufferPointer &other) { const MyBuffer *op = other.bufptr_; if (op == NULL) { bufptr_ = NULL; } else { bufptr_ = getbuf(op->length); bufptr_->length = op->length; std::size_t sz = op->length * sizeof op->data[0]; std::memmove( bufptr_->data, op->data, sz ); } return *this; } ~MyBufferPointer() { if (bufptr_) free(bufptr_); } std::size_t size() const { return bufptr_ ? bufptr_->length : 0; } // conventience operations for access to the data array: char &operator [](std::size_t index) { return bufptr_->data[index]; } char at(size_t index) const { return bufptr_->data[index]; } MyBuffer* c_buffer() { return bufptr_; } }; #include <iostream> using namespace std; int main() { MyBufferPointer bufp; cout << "bufp().size() = " << bufp.size() << ", c_buffer=" << bufp.c_buffer() << endl; bufp = MyBufferPointer(100); cout << "bufp().size() = " << bufp.size() << ", c_buffer=" << bufp.c_buffer() << endl; return 0; } 

The MyBuffer structure is a layout for the C data region, only with private constructor declarations and assignment statements to prevent instantiation or copy attempts (none of them will work properly, neither in C nor in C ++). The MyBufferPointer class encapsulates this as a C ++ class char [] array, overloading the [] operator.

It still uses malloc (), not the new one. The memory image needed to satisfy the C APIs you mentioned needs a variable-length structure, and you can't get it in the standard C ++ class created by the new one. It just provides a C ++ wrapper to give one point to create the structure in this class (in the static member functions getize () and getbuf ()); and guaranteed buffer deletion when the pointer goes out of scope. You can add resize (), to_string (), substring () or any other methods you want.

Performance should be identical to the C structure, accessible by a regular pointer after optimization, since the methods are declared in the class and are simple enough to be nested.

+1
source

Source: https://habr.com/ru/post/1500961/


All Articles