4

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?

sifferman
  • 2,955
  • 2
  • 27
  • 37
  • 1
    I don't believe you will be able to do this legally, although I can't see an implementation that would make this case not work. Do you truly just have an array in `A` and `B` just has separate members? – NathanOliver Apr 16 '18 at 19:27
  • Sounds just a little bit like `union`s, but I don't think that that's right. – zero298 Apr 16 '18 at 19:32
  • @NathanOliver Yes, though in my actual application there are 12 `char` variables, not 4, and `A` is a large buffer whose first 12 bytes are a header that constitutes the data I want `B` to contain. But I simplified the problem to 4 `char`s for the question. – sifferman Apr 16 '18 at 19:32
  • I think you should read [What does an object look like in memory](https://stackoverflow.com/questions/12378271/what-does-an-object-look-like-in-memory). In short, I think it could be possible but tricky. One of the possible issues is the undefined amount of padding between the members, which may not match the array. – GolezTrol Apr 16 '18 at 19:37
  • @sifferman Is `A` a standard layout class? – NathanOliver Apr 16 '18 at 19:39

4 Answers4

6

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
SergeyA
  • 61,605
  • 5
  • 78
  • 137
6

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

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
-2

Yes you can use an existing buffer to construct a new object into it. You would simple use the placement new operator:

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

char A[4];


B *b = new(A) B; 

Now b uses the stack memory of A[4]; The code assumes that B is packed without any alignment.

class D : public B
{
public:
   void MyMethod();
};

D *d = new (A) D;

You can add code in a derived class and then use the same approach constructing D.

The following code:

#include <new>

#include <iostream>

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

char A[4] = { 'M','e','o','w' };

int main()
{
    B *b = new (A) B;

    std::cout << "The cat's " << b->x1 << b->x2 << b->x3 << b->x4 << std::endl;

    return 0;
}

Outputs: The cat's Meow

Markus Schumann
  • 7,636
  • 1
  • 21
  • 27
  • 4
    Undefined behavior all the way. There is no guarantee whatsoever that `b` will have the same values as original `A`, those `xN` variables would be used uninitialized. – SergeyA Apr 16 '18 at 19:40
  • 1
    Whoever upvoted this answer (twice) did a disservice to the community. – SergeyA Apr 16 '18 at 20:08
  • Bjarne Stroustrup originally observed, in his book The Design and Evolution of C++, that pointer placement new is necessary for hardware that expects a certain object at a specific hardware address. It is also required for the construction of objects that need to reside in a certain memory area, such as an area that is shared between several processors of a multiprocessor computer. Stroustrup, Bjarne (1994). "Memory Management". Design and Evolution of C++. – Markus Schumann Apr 16 '18 at 20:50
  • And how is related to the incorrect assumption of yours that such placement new preserves original values existed in the memory? – SergeyA Apr 16 '18 at 20:57
  • Bjarne Stroustrup explains the use cases of the placement new operator. I just tried it in Visual Studio 2017 - it preserves the buffer A just fine. You run into problems if A doesn't match B's memory layout but the placement new operator will not initialize or modify the provided memory. Otherwise placement new would defeat its very own purpose. – Markus Schumann Apr 16 '18 at 21:14
  • 1
    No, it is you who doesn't understand. In the book it clearly says that the purpose of the placement new is to construct an object in the pre-defined memory (unlike generic new, which constructs the object somewhere). An example of shared memory or specific hardware address is one of those cases. Never it says that is designed to **preserve the data** in the given memory. Your test results of data being preserved are irrelevant, see link posted in the comments of the similar incorrect answer above yours. – SergeyA Apr 16 '18 at 21:20
  • Working code is relevant. There is no need for mentioning preserving data. In general, C++ implementations obey the zero-overhead principle (Bjarne Stroustrup) - there is nothing that will modify the buffer. – Markus Schumann Apr 16 '18 at 21:37
  • 1
    The solution you proposed is sited in a core c++ language issue: [issue 1997](http://open-std.org/jtc1/sc22/wg21/docs/cwg_active.html). – Oliv Apr 17 '18 at 06:06
-2

Yes, you can; if you use placement new, as asked here, (https://isocpp.org/wiki/faq/dtors#placement-new), which will specify the memory location for B and documented here (https://isocpp.org/wiki/faq/dtors#placement-new),

But beware of the destructor behavior in using this, and initializers in B would mess things up by overwriting the data. You also need to be very careful about memory alignment and any requirements that the objects have in that regard.

Specific warnings to regard on that page:

You are also solely responsible for destructing the placed object. This is done by explicitly calling the destructor:

ADVICE: Don’t use this “placement new” syntax unless you have to. Use it only when you really care that an object is placed at a particular location in memory. For example, when your hardware has a memory-mapped I/O timer device, and you want to place a Clock object at that memory location.

and DANGER: You are taking sole responsibility that the pointer you pass to the “placement new” operator points to a region of memory that is big enough and is properly aligned for the object type that you’re creating. Neither the compiler nor the run-time system make any attempt to check whether you did this right. If your Fred class needs to be aligned on a 4 byte boundary but you supplied a location that isn’t properly aligned, you can have a serious disaster on your hands (if you don’t know what “alignment” means, please don’t use the placement new syntax). You have been warned.

Halcyon
  • 1,376
  • 1
  • 15
  • 22
  • 3
    Same mistake as above. Undefined behavior. – SergeyA Apr 16 '18 at 19:40
  • 1
    Genuine question. I'm curious which part of it is undefined? Placement new specifically does not call the destructor, and if you do not initialize data then you won't overwrite the pre-existing buffer. The purpose of placement new is specifically to use memory that has already been allocated, but has a few things to keep in mind when using it. – Halcyon Apr 16 '18 at 19:48
  • 3
    There is absolutely no wording in the standard about placement new preserving the values of the previous object. It creates a new object, reusing the space. – Bo Persson Apr 16 '18 at 19:58
  • 1
    Because you can't be sure that the memory won't be zeroed: [Does placement new zero out the memory?](https://stackoverflow.com/q/10490191/691711) – zero298 Apr 16 '18 at 19:58
  • Placement new allows you to reuse *memory*, but not *data*. Reading from uninitialized variables is outright UB, you can't create a new object using placement new, and expect its' uninitialized fields to have specific values. – HolyBlackCat Apr 16 '18 at 20:00
  • @HolyBlackCat That link in the answer talks about placing a clock object into hardware memory with placement new and calls it a legit use. How could it be legit? – Killzone Kid Apr 16 '18 at 20:06
  • 3
    @KillzoneKid I don't think it's well-defined (note that the link doesn't say that), but it should be good enough in real world. – HolyBlackCat Apr 16 '18 at 20:11
  • Hmmm, true, I have to say I probably wouldn't ever use it because if it ever got compiled somewhere where it did get zeroed, then someone's life would have a noticeable drop in quality. I imagine there is a better way to rewrite the code that is much easier to maintain and reason about. Might be the case of an XY problem – Halcyon Apr 16 '18 at 20:16
  • 1
    @HolyBlackCat if you already going with undefined behavior, why go the lengths of placment new? Just alias the type. – SergeyA Apr 16 '18 at 20:16