> Let's first clarify the POD (Plain Old Data) and none-POD myth.
>
> First there is no distinction between "POD" and "none-POD". For any
> complicated class you can think about, they can be boiled down to base
> classes, "none-POD" data object members and POD data members. And each
> of the base class or "none-POD" member can be further taken apart into
> smaller components. Continue this process down, eventually every thing
> is composed of just POD data members.
>
> All classes can be boiled down to just POD data members and nothing else
>
First, let's speak one language.
Standard clearly specifies what is a POD:
"A POD-struct is an aggregate class that has no non-static data
members of type pointer to member, non-POD-struct, non-POD-union
(or array of such types) or reference, and has no user-defined copy
assignment operator and no user-defined destructor." (9/4)
Standard clearly specifies what is an aggregate:
"An aggregate is an array of a class with no user-declared constructors,
no private or protected non-static data members, no base classes, and
no virtual functions." (8.5.1/1)
There are defect reports on these definitions regarding PODs with
user-defined "address-of" operators, non-static "const" members etc,
but the intent is clear.
Second, _nothing_ is guaranteed about memory layout of non-POD objects.
It is _not_ guaranteed that it is contiguous.
It is _not_ guaranteed that address of object is the address of its initial element.
It is _not_ guaranteed that there are not traps inside.
Third, I want my copy assignment operators and copy constructors
work as intended.
Not blindly copied.
I want members of my class assigned and copied as it were intended
by their authors.
Not blindly copied.
The fourth.
Please take a courage to compile and run the following snippet
(IIRC it was posted by Francis Glassborow or James Kanze):
#include <iostream>
struct A { char ia; };
struct B : virtual A { char ib; };
struct C : virtual A { char ic; };
struct D : B, C { char id; };
void print( const B& b )
{
std::cout << " sizeof(b) = " << sizeof(b) << '\n';
std::cout << " &ia-&ib = " << ( &b.ia - &b.ib ) << '\n';
}
int main()
{
std::cout << "complete object B:\n";
print( B() );
std::cout << "B subobject of D:\n";
print( D() );
}
Output (VC.NET):
complete object B:
sizeof(b) = 9
&ia-&ib = 4
B subobject of D:
sizeof(b) = 9
&ia-&ib = 16
I think it would say you a lot about contiguous objects and sizeof.
And the fifth.
When Standard says "it is an undefined behaviour",
it is an undefined behaviour.
End of the statement.
> We have already seen that qsort() works on array of classes with
> std::string or std::vector and other class members.
>
What you have seen was an undefined behaviour.
An undefined behaviour works exactly in this way:
you _might_ see something you expected to see.
However, undefined behaviour _might_ also cause formatting
of your harddrive (please use Google to find out all the
possible consequences of undefined behaviour posted here
on comp.lang.c++ either by John Harrison or Neil Butterworth, iirc).
> Is such class POD or none-POD?
Such class might be an aggregate, but it is certainly non-POD.
>
> I made the assertion that if an object's state or property depends on
> its own memory location, then such object can not be properly copied.
> Some one cited the base_pointer example. Unfortunately this example
> exactly shows why such object can not be copied. You think your copy
> constructor works. But it doesn't!
Thank you for your inspiration.
Unfortunately, it does work.
Would you be so kind to elaborate?
Indeed, code I posted was not intended to compile.
If you are interested I can post a real code.
Nobody is assured against mistakes, but I don't think this is the case.
The only thing about based_ptr is that it uses non-standard and non-portable
functions offset_to_pointer<> and pointer_to_offset.
But it isn't related to the topic.
>
> Let's say your class contains a direct pointer pointing to the n'th char
> of a char array within the class. And there is a function call to get
> that pointer. The caller can use that pointer to do some low level data
> manipulation efficiently. And when you copy objects of this class
> around, you adjust that pointer so it still points to the correct char
> in the char array of the new object. Does it seem to be OK?
Yes, it is strange design, but it works:
#include <iostream>
#include <vector>
struct foo
{
char c[10];
char* p;
static const int N= 2;
// enum { N= 2 };
// just in case your compiler doesn't accept const int
foo() : p( &c[N] ) { strcpy(c, "A test"); }
foo( const foo& f ) : p( &c[N] ) { memcpy(c, f.c, sizeof(c) ); }
foo& operator= ( const foo& f ) { memcpy(c, f.c, sizeof(c) ); return *this; }
// Function get_pointer() is intended to allow clients of class
// to perform low-level data manipulations efficiently.
// The pointer returned may be invalidated by the following uses
// of foo object:
// - calling non-const member functions, including copy assignment operator
char* get_pointer() const { return p; }
};
int main()
{
foo f;
std::cout << f.get_pointer() << '\n';
foo other_f= f;
std::cout << other_f.get_pointer() << '\n';
foo yet_another_f( other_f );
std::cout << yet_another_f.get_pointer() << '\n';
std::vector<foo> vector_of_f;
vector_of_f.push_back( f );
vector_of_f.push_back( other_f );
std::cout << vector_of_f[ 0 ].get_pointer() << '\n'
<< vector_of_f[ 1 ].get_pointer() << std::endl;
}
Output:
test
test
test
test
test
>
> It's NOT OK.
??
> Let's say a piece of code calls to get that pointer, and
> decides to store the pointer for later usage.
Ok.
> And let's say the class object is the first object stored in a vector.
> And now you push one more
> object into the vector, and it finds it needs to do a memory
> reallocation to be able to contain more objects, and so all objects are
> "properly" copied to their new location.
> Every thing works so far.
Agree.
> But when the code who stored the previous
> pointer comes back to manipulate
> the data of the first object.
> Bom! Access violation!
Nope.
First, it isn't an access violation, it is an undefined behaviour.
Second, if user is stupid and/or never reads documentation,
everything might happen even with std::vector<int>.
Even with std::string.
You can protect stupid user from himself by removing
dangerous get_pointer().
Does the following snipped suprise you?
#include <string>
#include <iostream>
int main()
{
std::string s( "Hello, world" ), s2;
const char* p= s.c_str();
s.swap( s2 );
std::cout << p << std::endl; // Boom! Access violation!
}
No, it doesn't suprise you.
Standard says:
"References, pointers, and iterators referring to the elements
of a basic_string sequence may be invalidated by the following
uses of that basic_string object:
- [...] As an argument to basic_string::swap()." (21.3/5)
So the user _is_ warned about consequences.
Writing such a code the _user_ (not you, and not library vendors)
gets all the responsibility.