Re: C++ equivalent of C FAMs and subsequent issues with offsetof
On 2007-06-09, Carl Barron <cbarron413@adelphia.net> wrote:
In article <f4afpb$kj2$1@news.Stanford.EDU>, Seungbeom Kim
<musiphil@bawi.org> wrote:
How likely is it for the next version of C++ to incorporate such a
feature of C99, of the last member of a struct having an incomplete
array type?
struct foo
{
int len;
double data[];
};
what is sizeof(foo)? what does new foo; produce? or bet yet what
does new foo[10] produce?
what is it going to do with
struct bar
{
int n;
std::string lines[];
}; ?
I'd say that this is not likely, the apparent waste of a double
above makes the answers obvious ,even if it is useless, and your
special allocation functions only need to forget about the extra
declared entry, assuming the c'ism works, all is well, little or
nothing is gained by allowing empty array declarations in structs.
To answer your first question, the same rule as ISO 9899:1999 6.2.7.1
p16 would be most consistent. (i.e. sizeof(foo) is equivalent to
offsetof(foo1, data) in struct foo1 {int len; double data[1];} .
new foo and new foo[10] would create foo instances with a data member
such that data[0] is the element one beyond the end of the array.
This would be the same as for static and automatic storage variables
of foo type.
foo only becomes useful when created dynamically with a custom
allocator and I there is no reasonable way of new[]'ing an array of
such foo that I can see.
In the bar case, I think that it would be possible to make things work
although I'm not as convinced of the benefit. I wouldn't object to
restricting FAMs to POD types.
I appreciate that considering all corner cases and constructing
unambiguous and clear wording for a standard would be a lot of effort
so it would take considerable collective desire for this feature to be
considered but I would definitely be in favour of it. It would be a
shame for C and C++ to diverge more than necessary.
Here is how I'd envision creation and destruction working for bar.
Allocation and deallocation of the memory is matched as are
constructors and destructors (via placement new and direct destructor
invocation.
e.g.:
#include <new>
#include <string>
using std::string;
struct bar { int n; std::string lines[]; };
bar* MakeNLineBar(int lines)
{
void* pMem = ::operator new(sizeof(bar) + lines *
sizeof(std::string));
bar* pNewBar = new(pMem) bar;
pNewBar->n = lines;
for (int j = 0; j < lines; ++j)
{
new (&pNewBar->lines[j]) std::string;
}
return pNewBar;
}
void DestroyBar(bar* pBar)
{
for (int j = 0; j < pBar->n; ++j)
{
// Ewwww for syntax!
pBar->lines[j].~string();
}
// just a struct, but we did "placement new" it so for symmetry:
pBar->~bar();
::operator delete(pBar);
}
Interesting, this compiles as written in g++ but gives the error:
ISO C++ forbids zero-size array ??????lines??????
in -std=c++98 -pedantic mode. The "using std::string" seemed
necessary to make the explicit destructor call syntax work but would
otherwise be unnecessary.
In a C++ world, if we allowed construtors and destructors in bar, we
could have something close to my original pattern. This enables
"delete pBar" to work as expected.
#include <new>
#include <string>
using std::string;
struct bar { int n; std::string lines[]; bar(int); ~bar(); };
// nn must match the number of strings that can fit in the extra space
// that has been allocated for our FAM. Perhaps best to make this
// private and have a public Create function that ensures this.
bar::bar(int nn)
: n(nn)
{
// TODO: check n is reasonable, i.e. +ve and not too big
for (int j = 0; j < n; ++j)
{
new (&lines[j]) std::string;
}
}
bar::~bar()
{
for (int j = 0; j < n; ++j)
{
lines[j].~string();
}
}
Again, this compiles with g++.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]