5

EDIT: The main intent is to allow manipulating underlying data as part of an encapsulated struct as opposed to direct data manipulation.

Which of the following approaches is recommended when it comes to wrapping some data inside a struct:

  1. Keep a pointer to the data within the struct:

    new s(buf), which stores buf in a local field (s->buf = buf)

  2. reinterpret_cast-ing a memory address to a struct:

    reinterpret_cast<s*>(buf)

  3. Use the new operator against the memory address where the data is located:

    new(buf) s;

Here is a sample program for these approaches:

#include <iostream>
using namespace std;

struct s {
    int* i;
    s(int* buf) : i(buf) {}
    int getValue() { return *i * 2; }
};

struct s2 {
    int i;
    int getValue() { return i * 2; }
};

int main() {
    int buf = 10;
    s a(&buf);
    cout << "value: " << a.getValue() << ", size: " << sizeof(a) << ", address: " << &a << ", buf-address: " << &buf << endl;

    s2* a2 = new(&buf) s2;
    cout << "value: " << a2->getValue() << ", size: " << sizeof(*a2) << ", address: " << a2 << ", buf-address: " << &buf << endl;

    s2* a3 = reinterpret_cast<s2*>(&buf);
    cout << "value: " << a3->getValue() << ", size: " << sizeof(*a3) << ", address: " << a3 << ", buf-address: " << &buf << endl;
}

And the output:

value: 20, size: 4, address: 0027F958, buf-address: 0027F964
value: 20, size: 4, address: 0027F964, buf-address: 0027F964
value: 20, size: 4, address: 0027F964, buf-address: 0027F964

Both size & time are important. Also, maintainability is important, e.g. someone might add by mistake a virtual function to s2 (which will mess up the data alignment).

Thanks!

Nick
  • 5,765
  • 5
  • 27
  • 36

4 Answers4

4

None of those are even remotely good ideas, although the first one is passable with some modifications. reinterpret_cast doesn't work the way you think it does, and I'm not sure what exactly you're trying to achieve with placement new. Store a smart pointer of some sort in the first one to avoid the obvious issues with lifetime, and the first option isn't bad.

There is a fourth option: just store the data in a struct, and provide whatever encapsulated access you desire.

struct data {
    data(int i_) : i(i_) { }
    int i;
};

struct s {
    s(int i_) : i(i_) { }
    data i;
};

Rereading your question, it appears as though maybe your intent is for this struct to be an internal detail of some larger object. In that case, the lifetime issues with the first solution are likely taken care of, so storing a raw pointer is less of a bad idea. Absent additional details, though, I still recommend the fourth option.

Dennis Zickefoose
  • 10,791
  • 3
  • 29
  • 38
  • What if the data is stored as raw bytes? What are my options to encapsulating it inside a struct without any additional memory consuption? – Nick Jun 20 '11 at 16:30
  • @Nick I believe he's saying to do it the other way: Store the data as a struct and interpret it as raw bytes when needed. – Mark B Jun 21 '11 at 13:32
3

Placement new will still call the constructor, wiping out anything that's in the buffer already if such a constructor exists (or is created unknowingly in the future) so I don't think that's a safe option. reinterpret_cast is undefined behavior even though it may appear to work for you. Storing a local pointer seems to be the best option although you've only given a very tiny inkling of what you're trying to do.

If you're attempting serialization here, remember important issues like sizeof(int) and endianness.

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • Thanks -- btw, the main intent is to allow manipulating underlying data as part of an encapsulated struct as opposed to direct data manipulation. – Nick Jun 20 '11 at 15:12
1

Using reinterpret_cast with anything other than char* is an undefined behavior. So 2/ is obviously out.

1 and 3 are OK but 1/ is the most straightforward.

Joel Falcou
  • 6,247
  • 1
  • 17
  • 34
  • 1
    Are you sure about that? I'd say that with POD data, this should be fine – sehe Jun 20 '11 at 15:16
  • no, reinterpret_cast is an UB if you use it for anything else than aliasing to char*, void* and integral types. See http://stackoverflow.com/questions/573294/when-to-use-reinterpret-cast – Joel Falcou Jun 20 '11 at 15:18
  • 1
    @Joel: `reinterpret_cast` between pointer types does not give undefined behaviour, and the question you link to says nothing of the sort. – Mike Seymour Jun 20 '11 at 15:45
  • 1
    That's overly simplistic. `reinterpret_cast` can be used to turn a pointer to one type into a pointer to any other type, you just can't do anything with that new pointer except use `reinterpret_cast` to get the original value back, and that is subject to certain alignment restrictions. `char*` receives no special treatment in this regard. Either way, though, you are correct that as used in the question, the behavior is simply not safe. – Dennis Zickefoose Jun 20 '11 at 15:45
  • THe observation about reinterpret_cast is very worrying -- in my case I am working with raw data as input, which means manipulating raw data as reinterpret_cast structs is not guaranteed to work – Nick Jun 20 '11 at 16:29
  • @Dennis yeah sorry, got some hyper-reductive comment there. – Joel Falcou Jun 20 '11 at 16:30
1

You may prefer to encasulate data, you may want to use a (void*) pointer to a struct or class, these allows type encapsulation, and allow to extend the data in your struct, in next versions of your code:

struct HiddenCode{
  int Field1;
  char Field2;
};

void transferData(void* anyptr)
{
    // this method "knows" that "anyptr" is a "HiddenCode*"
    HiddenCode* MyHiddenCode = (void*) anyptr;
    // do something else
}

void main()
{
  HiddenCode* MyHiddenCode = new HiddenCode();
  MyHiddenCode->Field1 = 5;
  MyHiddenCode->Field2 = '1';

  void* anyptr = (void*)MyHiddenCode;
  transferData(anyptr);
}

Cheers.

umlcat
  • 4,091
  • 3
  • 19
  • 29
  • That also has the benefit of allowing your users the flexability to pass any type they want to `transferData`. `void* p = (void*)new int; transferData(p);` works just as well; that sort of convenience should not be overlooked. – Dennis Zickefoose Jun 20 '11 at 15:55
  • So we are talking about reinterpret_cast, right? Or... is this static_cast? – Nick Jun 20 '11 at 16:10
  • @Nick If are using a struct as non object, then you should use "static_cast", if its an object, then "reinterpret" cast – umlcat Jun 20 '11 at 16:20
  • @umlcat -- if raw data is my input, apparently according to this link and @Dennis Zickefoose, reinterpret_cast is not guaranteed to work: http://stackoverflow.com/questions/573294/when-to-use-reinterpret-cast – Nick Jun 20 '11 at 16:27
  • @Nick Sorry, I really want to say: If are using a struct as non object, then you should use "static_cast", if its an object, then "dynamic_cast". Check the "this method "knows" that "anyptr" is a "HiddenCode*" " comment – umlcat Jun 20 '11 at 16:35
  • Ok -- btw, there seems to be an error there: `HiddenCode* MyHiddenCode = (void*) anyptr` – Nick Jun 20 '11 at 16:38