Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I construct an object without allocating memory or copying data?

Consider a class 'B' that contains simple char member vars in a certain order.

class B {
    char x1;
    char x2;
    char x3;
    char x4;
}

I have a buffer A that already contains data in the same order as defined in B. Another process has already loaded A with the data.

char A[4];
  1. Is it possible to construct an object of type B that contains the data of A, without the constructor copying the data? That is, I want to "overlay" a B object onto the A buffer so I can use B methods on the data, without incurring the overhead of a copy, or the allocation of memory.

  2. Assuming there is a solution to question 1, would there be any reason I couldn't also define a class D that derives from B and which has methods that reference the member vars of B, but which itself contains no new member variables?

like image 495
sifferman Avatar asked Jan 29 '23 07:01

sifferman


2 Answers

Since an A is not a B there is no legal way to treat an A as a B. That said, if A is a standard layout class then you should be able to cast it and it will "Work" but it wont be legal. For example

struct A
{
    char data[6] = "hi 0/";
    int a = 10;
    int b = 20;
};

struct B
{
    char x1;
    char x2;
    char x3;
    char x4;
    char x5;
    char x6;
};

std::ostream& operator <<(std::ostream& os, B& b)
{
    return os << b.x1 << b.x2 << b.x3 << b.x4 << b.x5 << b.x6;
}

int main()
{
    A a;
    B* b = reinterpret_cast<B*>(&a);
    std::cout << *b;
}

This works since the array and the members occupy the same section of memory in each class but this isn't guaranteed. There could be padding after x1 in B which would mean not all members will be mapped to the array.

Now, if you rework B to have an array instead of separate members like

struct A
{
    char data[6] = "hi 0/";
    int a = 10;
    int b = 20;
};

struct B
{
    char data[6];
};

Then you could uses a Union to hold both A and B and since B has the same common initial sequence as A it is legal to use b. That could look like

union Converter
{
    Converter() : a{} {}
    A a;
    B b;
};

std::ostream& operator <<(std::ostream& os, B& b)
{
    return os << b.data;
}

int main()
{
    Converter c;
    std::cout << c.b;
}

And now the cast is gone and we have a guarantee from the standard that this is safe

like image 59
NathanOliver Avatar answered Jan 30 '23 20:01

NathanOliver


As unpleasant as it is, there is no legal (Standard-wise) way to achieve that. Instead, you have one illegal, but usually working one (used across plethora of places) or legal, but optimization-dependent.

Regardless of method, this assumes that there is no padding for B members (add [[gnu::packed]] for gcc, or something else for your compiler to B definition to ensure no padding is happening).

First would be illegal one - you can alias the type. This violates strict aliasing rule, but is known to work on many platforms and compilers. Code sample:

const B* b = reinterpret_cast<const B*>(&a[0]);

The second option is to rely on compiler's optimizer. It is often powerful enough to realize there is no need to actually copy the data, and will just use original values. It doesn't happen all the time, and if you rely on this technique in performance-critical section, you better check the generated code and recheck it with every compiler upgrade.

This code assumes an optimization:

B b;
memcpy(&b, &a[0], sizeof(b));
// use b in non-modifying way
// Compilers usually will not issue a copy here, YMMV
like image 25
SergeyA Avatar answered Jan 30 '23 20:01

SergeyA