Rebirthing an object... just making sure

  • Thread starter Tomás Ó hÉilidhe
  • Start date
T

Tomás Ó hÉilidhe

I have an object that represents a listbox on a GUI dialog. In order
to clear the listbox, I can simply delete the listbox and then create
another one. For instance:

// Snippet A

delete p_list;

p_list = new ListBox();

There's the alternative method though that avoids re-allocating memory:

// Snippet B

p_list->~ListBox();

::new(p_list) ListBox();

My question is as follows:
If you're able perform snippet A on a particular object, then are you
ALWAYS able to perform snippet B instead?

See I can't think of any reason why Snippet B wouldn't always work
perfectly... but for some reason it strikes me as one of those situations
where you might overlook something and only realise it a week later, so I
thought I'd check with you guys.
 
A

Alf P. Steinbach

* "Tom��������������������������������":
I have an object that represents a listbox on a GUI dialog. In order
to clear the listbox, I can simply delete the listbox and then create
another one. For instance:

// Snippet A

delete p_list;

p_list = new ListBox();

There's the alternative method though that avoids re-allocating memory:

// Snippet B

p_list->~ListBox();

::new(p_list) ListBox();

My question is as follows:
If you're able perform snippet A on a particular object, then are you
ALWAYS able to perform snippet B instead?

I think so, yes, but the two code snippets are not equivalent.

In the first snippet, if ListBox() throws, the listbox object's memory
has been deallocated. In the second snippet, the memory has not been
deallocated. And here there's no easy way to deallocate that memory if
ListBox() throws.

Secondly, I had to check the standard about whether the second snippet
correctly handles a virtual destructor for the case of p_list pointing
to an instance of a derived class. Code that requires standard-checking
is a little ungood for maintainance. And in general, too "clever" code.

See I can't think of any reason why Snippet B wouldn't always work
perfectly... but for some reason it strikes me as one of those
situations where you might overlook something and only realise it a week
later, so I thought I'd check with you guys.

In addition to above comments, clearing a listbox that way could lead to
on-screen flicker, the cost of dynamic allocation in the context of GUI
operations is generally negligible, relatively speaking, and the only
dynamic allocation you avoid is for the listbox object itself, not for
constituents that are referred via pointers or handles.

Use the listbox'es clear function.

Don't optimize prematurely.

Cheers, & hth.,

- Alf
 
T

Tomás Ó hÉilidhe

Alf P. Steinbach said:
In the first snippet, if ListBox() throws, the listbox object's memory
has been deallocated. In the second snippet, the memory has not been
deallocated. And here there's no easy way to deallocate that memory
if ListBox() throws.


Each dialog box has its own class, something like:

class MonkeyDialog : public Dialog {
public:

ListBox *p_list;
};

The value of p_list is set in the constructor. Presumably like so:

void MonkeyDialog::CreateControls(void)
{
p_list = new ListBox();
}

MonkeyDialog::MonkeyDialog() : p_list(0)
{
CreateControls();
}

MonkeyDialog::~MonkeyDialog()
{
delete p_list;
}

In Snippet B that I originally posted, if the contructor throws, then
"p_list" will contain a non-null address of a has-been-deleted objected.
Therefore, I think I'd have to change Snippet B to:

ListBox *const ptemp = p_list;
delete p_list;
p_list = 0;
::new(ptemp) ListBox();
p_list = ptemp;

....which would probably be better as:

template<class T>
void RebirthObject(T *&pobj)
{
T *const ptemp = pobj;
delete pobj;
pobj = 0;
::new(ptemp) T();
pobj = ptemp;
}

In addition to above comments, clearing a listbox that way could lead
to on-screen flicker, the cost of dynamic allocation in the context of
GUI operations is generally negligible, relatively speaking, and the
only dynamic allocation you avoid is for the listbox object itself,
not for constituents that are referred via pointers or handles.

Use the listbox'es clear function.


The abstract base wxControlWithItems has a method called Clear.
wxRadioBox was intended to inherit from wxControlWithItems, but for some
reason it doesn't. Therefore, the only way to clear the list is to
rebirth.

Don't optimize prematurely.


Admittedly I'm doing it more for kicks than anything else.
 
D

dizzy

Tomas said:
In Snippet B that I originally posted, if the contructor throws, then
"p_list" will contain a non-null address of a has-been-deleted objected.
Therefore, I think I'd have to change Snippet B to:

ListBox *const ptemp = p_list;
delete p_list;
p_list = 0;
::new(ptemp) ListBox();
p_list = ptemp;

Maybe I miss something but isn't that clear UB? You give to inplace new a
pointer "ptemp" which points to invalid memory (you deleted the same
location above it).

T* ptr2 = ptr1;
delete ptr1; // after this both ptr1 and ptr2 to point to invalid memory
 
J

James Kanze

Each dialog box has its own class, something like:

class MonkeyDialog : public Dialog {
public:
ListBox *p_list;
};
The value of p_list is set in the constructor. Presumably like so:
void MonkeyDialog::CreateControls(void)
{
p_list = new ListBox();
}
MonkeyDialog::MonkeyDialog() : p_list(0)
{
CreateControls();
}
MonkeyDialog::~MonkeyDialog()
{
delete p_list;
}
In Snippet B that I originally posted, if the contructor
throws, then "p_list" will contain a non-null address of a
has-been-deleted objected. Therefore, I think I'd have to
change Snippet B to:
ListBox *const ptemp = p_list;
delete p_list;

You doubtlessly mean "p_list->~ListBox" here.
p_list = 0;
::new(ptemp) ListBox();
p_list = ptemp;
...which would probably be better as:
template<class T>
void RebirthObject(T *&pobj)
{
T *const ptemp = pobj;
delete pobj;

Again: "pobj->~T()"
pobj = 0;
::new(ptemp) T();
pobj = ptemp;
}

And of course, if the constructor of T throws, you still leak
the memory. You really need something like:

template< typename T >
void
renewObject( T*& obj )
{
// Fail if object has a derived type...
assert( typeid( *obj ) == typeid( T ) ) ;
obj->~T() ;
try {
new( obj ) T ;
} catch ( ... ) {
::eek:perator delete( obj ) ;
obj = NULL ;
throw ;
}
}
 
T

Tomás Ó hÉilidhe

James Kanze said:
You doubtlessly mean "p_list->~ListBox" here.


Yes thanks for that.

Again: "pobj->~T()"


And again thanks.

And of course, if the constructor of T throws, you still leak
the memory. You really need something like:

template< typename T >
void
renewObject( T*& obj )
{
// Fail if object has a derived type...
assert( typeid( *obj ) == typeid( T ) ) ;
obj->~T() ;
try {
new( obj ) T ;
} catch ( ... ) {
::eek:perator delete( obj ) ;
obj = NULL ;
throw ;
}
}


I'm intending for there to be a leak, because the dialog's class code
deletes its controls automatically when the dialog is closed.
 
T

Tomás Ó hÉilidhe

dizzy said:
Maybe I miss something but isn't that clear UB? You give to inplace new a
pointer "ptemp" which points to invalid memory (you deleted the same
location above it).

T* ptr2 = ptr1;
delete ptr1; // after this both ptr1 and ptr2 to point to invalid memory


Yes you're absolutely right. I meant to call the destructor instead of call
delete... but my brain wasn't functioning very well at the time.
 
T

Tomás Ó hÉilidhe

Tomás Ó hÉilidhe said:
I'm intending for there to be a leak, because the dialog's class code
deletes its controls automatically when the dialog is closed.


Wups a daisy... about five seconds after I posted that I realised I'd
need a catch to set the pointer to null to avoid the dialog's class from
calling the destructor on an object that's already been destructed.

Anyway, in my own particular program, it's a fatal error if you can't
rebirth an object, so I think I'll go with:

template<class T>
void Rebirth(T &obj)
{
obj.~T();

try { ::new(&obj) T(); } catch (...) { exit(EXIT_FAILURE); };
}
 
J

jkherciueh

Alf said:
* "Tom????????????????????????????????":

I think so, yes, but the two code snippets are not equivalent.

In the first snippet, if ListBox() throws, the listbox object's memory
has been deallocated. In the second snippet, the memory has not been
deallocated. And here there's no easy way to deallocate that memory if
ListBox() throws.

Secondly, I had to check the standard about whether the second snippet
correctly handles a virtual destructor for the case of p_list pointing
to an instance of a derived class. Code that requires standard-checking
is a little ungood for maintainance. And in general, too "clever" code.

If the static and dynamic type of *p_list differ, snippet B has undefined
behavior. Well, more precisely, any code that follows the last line of
snippet B and treats *p_list as a pointer to a valid ListBox object has
undefined behavior. The reason is the very last item in [3.8/7]:

If, after the lifetime of an object has ended and before the storage which
the object occupied is reused or released, a new object is created at the
storage location which the original object occupied, a pointer that
pointed to the original object, a reference that referred to the original
object, or the name of the original object will automatically refer to the
new object and, once the lifetime of the new object has started, can be
used to manipulate the new object, if:

? the storage for the new object exactly overlays the storage location
which the original object occupied, and
? the new object is of the same type as the original object (ignoring the
top-level cv-qualifiers), and
? the type of the original object is not const-qualified, and, if a class
type, does not contain any non-static data member whose type is
const-qualified or a reference type, and
? the original object was a most derived object (1.8) of type T and the
new object is a most derived object of type T (that is, they are not
base class subobjects).

I think, the standard was written with a vtable implementation in mind where
the constructors of a class will set the vtaple pointer as their last
operation so that the most derived constructor sets the dynamic type of the
object. If you use placement new on a subobject, you therefore might change
the dynamic type of the ambient object involuntarily.


[snip]


Best

Kai-Uwe Bux
 
A

Alf P. Steinbach

* (e-mail address removed):
If the static and dynamic type of *p_list differ, snippet B has undefined
behavior. Well, more precisely, any code that follows the last line of
snippet B and treats *p_list as a pointer to a valid ListBox object has
undefined behavior.

No, a pointer to an object is a pointer to an object, regardless of what
the object's memory has been used for earlier.

The object's memory (in standardeeze, storage) is just bits.

The reason is the very last item in [3.8/7]:

If, after the lifetime of an object has ended and before the storage which
the object occupied is reused or released, a new object is created at the
storage location which the original object occupied, a pointer that
pointed to the original object, a reference that referred to the original
object, or the name of the original object will automatically refer to the
new object and, once the lifetime of the new object has started, can be
used to manipulate the new object, if:

? the storage for the new object exactly overlays the storage location
which the original object occupied, and
? the new object is of the same type as the original object (ignoring the
top-level cv-qualifiers), and
? the type of the original object is not const-qualified, and, if a class
type, does not contain any non-static data member whose type is
const-qualified or a reference type, and
? the original object was a most derived object (1.8) of type T and the
new object is a most derived object of type T (that is, they are not
base class subobjects).

I think you meant to write "the second item", not "the very last item".

What this list seems to be about is the situation

DerivedListBox* p1 = new DerivedListBox();
ListBox* p2 = p1;
p2->ListBox::~ListBox(); // OK if virtual destructor
::new(p2) ListBox(); // DOES NOT make p1 valid.

Having destroyed the original complete object there's no longer any
DerivedListBox object anywhere. So p1 is now invalid. p2 is good,
because pointers don't care what pointed to bits have been used for
earlier, only what they're used for now... ;-)

On the other hand,

ListBox* p = new DerivedListBox();
p->ListBox::~ListBox(); // OK if virtual destructor
::new(p) ListBox(); // OK (except leakage for exception...)

This last is OK because sizeof(ListBox) <= sizeof(DerivedListBox). But
having done this, knowledge of the amount of memory to deallocate is
lost if it isn't stored elsewhere. The only safe way to delete the
object that I can think of would be to use an explicit destructor call
followed by call to the relevant operator delete with the original size.

I think, the standard was written with a vtable implementation in mind where
the constructors of a class will set the vtaple pointer as their last
operation so that the most derived constructor sets the dynamic type of the
object. If you use placement new on a subobject, you therefore might change
the dynamic type of the ambient object involuntarily.

You might change the dynamic type, yes, but AFAICS vtable implementation
or not has nothing to do with it. The object is represented by its
storage anyway. And that storage is just bits, which retain no memory
of they have been used for.

Cheers,

- Alf
 
J

jkherciueh

Alf said:
* (e-mail address removed):

No, a pointer to an object is a pointer to an object, regardless of what
the object's memory has been used for earlier.

The object's memory (in standardeeze, storage) is just bits.

I don't know why it would matter what the pointer is. The standard says:

If, after the lifetime of an object has ended and before the storage which
the object occupied is reused or released, a new object is created at the
storage location which the original object occupied, a pointer that
pointed to the original object [that would be p_list] ... can be used to
manipulate the new object, if ...

Whatever the pointer _is_ is irrelevant. The standard specified conditions
under which that pointer may be used to manipulate the new object. If those
conditions are not met, you cannot use the pointer to manipulate the new
object. The nature of the pointer or the pointee just being bits is pretty
irrelevant.

The reason is the very last item in [3.8/7]:

If, after the lifetime of an object has ended and before the storage
which the object occupied is reused or released, a new object is
created at the storage location which the original object occupied, a
pointer that pointed to the original object, a reference that referred
to the original object, or the name of the original object will
automatically refer to the new object and, once the lifetime of the new
object has started, can be used to manipulate the new object, if:

? the storage for the new object exactly overlays the storage location
which the original object occupied, and
? the new object is of the same type as the original object (ignoring
the
top-level cv-qualifiers), and
? the type of the original object is not const-qualified, and, if a
class
type, does not contain any non-static data member whose type is
const-qualified or a reference type, and
? the original object was a most derived object (1.8) of type T and the
new object is a most derived object of type T (that is, they are not
base class subobjects).

I think you meant to write "the second item", not "the very last item".

No, I meant what I wrote. But you are correct that item 2 is also relevant.
What this list seems to be about is the situation

DerivedListBox* p1 = new DerivedListBox();
ListBox* p2 = p1;
p2->ListBox::~ListBox(); // OK if virtual destructor
::new(p2) ListBox(); // DOES NOT make p1 valid.

Having destroyed the original complete object there's no longer any
DerivedListBox object anywhere. So p1 is now invalid. p2 is good,
because pointers don't care what pointed to bits have been used for
earlier, only what they're used for now... ;-)

Nope. [3.8/7] talks about a "pointer that pointed to the original object"
and specifies whether it can be used to manipulate the new object. In the
example above, [3.8/7] applies to p1 and p2 as both point to the original
object. Neither of the pointers is good to go at the end of this snippet.
On the other hand,

ListBox* p = new DerivedListBox();
p->ListBox::~ListBox(); // OK if virtual destructor
::new(p) ListBox(); // OK (except leakage for exception...)

This last is OK because sizeof(ListBox) <= sizeof(DerivedListBox).

Nope. Read [3.8/7] again. You are destroying the ListBox subobject of a
derived object. Then you recreate a ListBox subobject in its place. Using p
to manipulate the new object will be UB by the last item in the list
because the objects involved are not most derived.

But
having done this, knowledge of the amount of memory to deallocate is
lost if it isn't stored elsewhere. The only safe way to delete the
object that I can think of would be to use an explicit destructor call
followed by call to the relevant operator delete with the original size.



You might change the dynamic type, yes, but AFAICS vtable implementation
or not has nothing to do with it. The object is represented by its
storage anyway. And that storage is just bits, which retain no memory
of they have been used for.

Huh?


Best

Kai-Uwe Bux
 
A

Alf P. Steinbach

* (e-mail address removed):
[string of misunderstandings]

Kai, after

::new(p) T();

with no exception and T() constructing a valid object, p is a valid
pointer to that object -- /no matter what that memory was used for
earlier/, garbage, similar object, whatever, that earlier bit usage
history /does not matter/.

If you manage to see a contradiction in the standard, then that just
means that most likely your interpretation is not applicable, or else
that there is a Defect in the standard which should then be reported.

Reporting of Defects is, however, as I understand it, not working
completely perfectly/reliably.


Cheers, & hth.,

- Alf
 
J

jkherciueh

Alf said:
* (e-mail address removed):
[string of misunderstandings]

Kai, after

Uhm -- it's Kai-Uwe. (The "Uwe" is not a middle name.)

::new(p) T();

with no exception and T() constructing a valid object, p is a valid
pointer to that object -- /no matter what that memory was used for
earlier/, garbage, similar object, whatever, that earlier bit usage
history /does not matter/.

Unfortunately (and since I was burnt by [3.8/7], I really mean it), [3.8/7]
explicitly says that the history does matter. In particular, according to
my reading

Base * ptr = new Derived;
ptr->Base::~Base(); // only destroy the Base subobject
::new (ptr) Base; // recreate a Base subobject
ptr->some_base_member();

has undefined behavior.

Now, I might be overly careful with regard to [3.8/7] since I have a history
with that clause. So, you could be right and I could be misunderstanding
something. However, adding emphasis to your claims will not help me learn.
So please bear with me and help me sorting this out.

I think, the main issue with regard to the snippet is that some_base_member
might be virtual. In that case, the implementation could invoke either
Derived::some_base_member or Base::some_base_member. Off hand, I don't see
a provision in the standard that guarantees that placement new will adjust
the dynamic type of ptr to Base nor a provision that it will leave the
dynamic type unchanged. Note that either way is compatible with a valid
Base object being created at the memory location designated by ptr [which
is all that placement-new seems to be guaranteed to accomplish].

Similarly (and for similar reasons), any subsequent

delete ptr;

has undefined behavior.

If you manage to see a contradiction in the standard, then that just
means that most likely your interpretation is not applicable, or else
that there is a Defect in the standard which should then be reported.

I do not (yet) see a contradiction in the standard. I would highly
appreciate if you would provide a deduction (from the standard and not from
liberal use of emphasis:) that the above snippet has well-defined
behavior. Supplying such an argument (addressing in particular the dispatch
of virtual functions), _you_ (not I) will deserve the credit for
identifying a contradiction in the standard.

Reporting of Defects is, however, as I understand it, not working
completely perfectly/reliably.

I am not convinced, as of now, that there is a defect.


Best

Kai-Uwe Bux
 
A

Alf P. Steinbach

* (e-mail address removed):
Alf said:
* (e-mail address removed):
[string of misunderstandings]
Kai, after

Uhm -- it's Kai-Uwe. (The "Uwe" is not a middle name.)

::new(p) T();

with no exception and T() constructing a valid object, p is a valid
pointer to that object -- /no matter what that memory was used for
earlier/, garbage, similar object, whatever, that earlier bit usage
history /does not matter/.

Unfortunately (and since I was burnt by [3.8/7], I really mean it), [3.8/7]
explicitly says that the history does matter. In particular, according to
my reading

Base * ptr = new Derived;
ptr->Base::~Base(); // only destroy the Base subobject
::new (ptr) Base; // recreate a Base subobject
ptr->some_base_member();

has undefined behavior.

Now, I might be overly careful with regard to [3.8/7] since I have a history
with that clause. So, you could be right and I could be misunderstanding
something. However, adding emphasis to your claims will not help me learn.
So please bear with me and help me sorting this out.

Yes, gladly, but I wasn't adding emphasis as a kind of loud voice, I
emphasized what to my mind was and is important for you to understand.

Consider:

char* buf = new char[sizeof(T)];
// Here copying whatever values you want into buf.
// Then:

T* p = static_cast<T*>( static_cast<void*>( buf ) );
T* q = ::new( p ) T();

Essentially your argument implies, in practice (see comment about RREPP
below for why it's not completely formalt), that

assert( ((void*)buf == (void*)p) && (p == q) );

will now fail. For if it doesn't fail, then we have constructed a T
object in memory that possibly could contain exactly the same as after
an explicit destructor call on a T object. Which by your interpretation
of the standard, disregarding RREPP, would be Undefined Behavior.

So standardwise all you need to disprove your interpretation is to prove
that the assertion above doesn't fail, i.e. that the pointers all are
the same address. Let's skip discussing (void*)buf == (void*)p. I
assume you agree: it would be very much ado about nothing.

Then, for p == q, to start with, §18.4.1.3/2 guarantees that the
single-object standard library placement operator new just returns the
argument pointer, as a void*. Proving that this is also the address
returned by the new expression is more difficult because AFAICS it's not
explicitly stated by the standard. However, first off the storage
provided need not be greater than sizeof(T), and secondly, non-normative
note §5.3.4/14 states that "The address of the created object will not
necessarily be the same that of the block if the object is an array",
and the above is not an array but a single object.

I guess this is as close to a proof of the assert above that you can get.

And with the assert proven to the degree possible with such vague
standard, you have proven that you can validly construct an object of
type T in memory that contains exactly the same as after an explicit
destruction of a T object, and then have a valid pointer.

And with that proven you have proven that any UB resulting from using an
object placement-constructed in memory that earlier held object of
different type, must come from information held elsewhere about the type
of that object and what address it resided in in memory.

So formally, if your interpretation is correct the only rationale could
be to support Really Really Extremely Phat Pointers (RREPP) that on
assignment checks the dynamic type of the pointed to object and stores
that type information, a typeid-call for every pointer assignment.



[misunderstandings snipped]

Cheers, & hth.,

- Alf
 
J

jkherciueh

Alf said:
* (e-mail address removed):
Alf said:
* (e-mail address removed):
[string of misunderstandings]

Kai, after

Uhm -- it's Kai-Uwe. (The "Uwe" is not a middle name.)

::new(p) T();

with no exception and T() constructing a valid object, p is a valid
pointer to that object -- /no matter what that memory was used for
earlier/, garbage, similar object, whatever, that earlier bit usage
history /does not matter/.

Unfortunately (and since I was burnt by [3.8/7], I really mean it),
[3.8/7] explicitly says that the history does matter. In particular,
according to my reading

Base * ptr = new Derived;
ptr->Base::~Base(); // only destroy the Base subobject
::new (ptr) Base; // recreate a Base subobject
ptr->some_base_member();

has undefined behavior.

Now, I might be overly careful with regard to [3.8/7] since I have a
history with that clause. So, you could be right and I could be
misunderstanding something. However, adding emphasis to your claims will
not help me learn. So please bear with me and help me sorting this out.

Yes, gladly, but I wasn't adding emphasis as a kind of loud voice, I
emphasized what to my mind was and is important for you to understand.

Consider:

char* buf = new char[sizeof(T)];
// Here copying whatever values you want into buf.
// Then:

T* p = static_cast<T*>( static_cast<void*>( buf ) );
T* q = ::new( p ) T();

Essentially your argument implies, in practice (see comment about RREPP
below for why it's not completely formalt), that

assert( ((void*)buf == (void*)p) && (p == q) );

will now fail. For if it doesn't fail, then we have constructed a T
object in memory that possibly could contain exactly the same as after
an explicit destructor call on a T object. Which by your interpretation
of the standard, disregarding RREPP, would be Undefined Behavior.

I think, you are oversimplifying the issue. In particular, you assume that
all that matters is the bit-pattern at the position of the object. However,
the information about dynamic types may be stored elsewhere. For instance,
there could be a global table mapping address-ranges to type-information.
Constructors would register the type of the pointee and destructors would
un-register. A virtual function call could be dispatched by searching the
table for the largest enclosing address range (aka most derived object).
Now, placement-new does not necessarily change that. It would just update
the table entry for the subobject.

Given such an implementation, the assertion above could hold, yet still
virtual function dispatch in the snippet I gave could go to
Derived::some_base_member whereas with a vtable implementation it could go
to Base::some_base_member.

So standardwise all you need to disprove your interpretation is to prove
that the assertion above doesn't fail, i.e. that the pointers all are
the same address. Let's skip discussing (void*)buf == (void*)p. I
assume you agree: it would be very much ado about nothing.

Then, for p == q, to start with, §18.4.1.3/2 guarantees that the
single-object standard library placement operator new just returns the
argument pointer, as a void*. Proving that this is also the address
returned by the new expression is more difficult because AFAICS it's not
explicitly stated by the standard. However, first off the storage
provided need not be greater than sizeof(T), and secondly, non-normative
note §5.3.4/14 states that "The address of the created object will not
necessarily be the same that of the block if the object is an array",
and the above is not an array but a single object.

I guess this is as close to a proof of the assert above that you can get.

Actually, I agree that the assert will hold. I do not agree, however, that
this resolves the issue about my snippet above.

And with the assert proven to the degree possible with such vague
standard, you have proven that you can validly construct an object of
type T in memory that contains exactly the same as after an explicit
destruction of a T object, and then have a valid pointer.

Here, I disagree. I think you are assuming that dynamic type information is
encoded somewhere in the bit pattern of the object. Only then does it
follow that the pointer can be used for virtual function calls.

And with that proven you have proven that any UB resulting from using an
object placement-constructed in memory that earlier held object of
different type, must come from information held elsewhere about the type
of that object and what address it resided in in memory.

Oh, now I see that we were in agreement about this all along :)

So formally, if your interpretation is correct the only rationale could
be to support Really Really Extremely Phat Pointers (RREPP) that on
assignment checks the dynamic type of the pointed to object and stores
that type information, a typeid-call for every pointer assignment.

These RREP-pointers are pairs (address,address) where the first points to
the object and the second to a type-descriptor (essentially putting the
vtable ptr into the pointer as opposed to the object)? In that case,
assignment could still be fast: if the rhs already is a pointer, only two
addresses need to be copied; if the rhs is a new-expression, the compiler
knows the second component from the type in the new expression. Only
operator& might involve a typeid-call (not sure, though). Virtual function
dispatch, on the other hand, now saves one level of indirection.

Also, why would that be the _only_ possible rationale? The address-range
based table-lookup implementation from above for virtual function dispatch
would allow for pretty small pointers and pointer assignment would be fast.
What would be expensive are virtual function calls. The rationale for a
table based dispatch mechanism could be a space-time tradeoff (although it
should be hard to beat vtable pointers space-wise, so it probably would
only save space if there are really really long inheritance chains).

Anyway, with regard to making sense of the standard, I think, the only
rationale for the provisions of [3.8/7] (especially the last item) is to
allow for such (moderately) weird implementations. If not, what would be
the point of the last item in [3.8/7]?


Best

Kai-Uwe Bux
 
J

James Kanze

Wups a daisy... about five seconds after I posted that I
realised I'd need a catch to set the pointer to null to avoid
the dialog's class from calling the destructor on an object
that's already been destructed.

Yes. And if you set the pointer to null, the dialog's class
can't free the memory, because it has no pointer to it. So you
need the ::eek:perator delete( obj ) in the catch as well.
 
J

James Kanze

[...]
So formally, if your interpretation is correct the only
rationale could be to support Really Really Extremely Phat
Pointers (RREPP) that on assignment checks the dynamic type of
the pointed to object and stores that type information, a
typeid-call for every pointer assignment.

I'm not sure how relevant it is here, but the authors of the
standard definitely did consider tagged architectures, where
each pointer also contains type information. (I believe the old
Burroughs processors had something along those lines. And of
course, more than one processor has used a tag to indicate
whether the pointer pointed to the final value, or to another
pointer.)
 
J

James Kanze

On Jan 11, 6:09 am, (e-mail address removed) wrote:

[...]
I think, you are oversimplifying the issue. In particular, you
assume that all that matters is the bit-pattern at the
position of the object. However, the information about dynamic
types may be stored elsewhere. For instance, there could be a
global table mapping address-ranges to type-information.
Constructors would register the type of the pointee and
destructors would un-register. A virtual function call could
be dispatched by searching the table for the largest enclosing
address range (aka most derived object). Now, placement-new
does not necessarily change that. It would just update the
table entry for the subobject.

I think the issue the committee actually considered was
optimization. When can an implementation cache the vptr in a
register, and when not. I've just skimmed your arguments, and
not verified anything in the standard, so I'm not sure what the
standard actually says, but I do know that there was some
discussion as to whether, given something like the following:

Base* p = new Derived ;
p->someFunction() ;
rebirth( p ) ;
p->anotherFunction() ;

could the compiler load the vptr (supposing that was the
implementation, of course) in a register when it called
someFunction, and reuse the value from the register when it
called anotherFunction.

I don't remember what exactly was finally decided, but you might
keep these sort of considerations in mind when reading the
standard; the committee definitely did consider possible
optimizations, in which the compiler kept values cached in
registers. (A lot of the undefined behavior in the standard is
designed to allow this sort of caching, which can make an
enormous difference in optimization.)
 
A

Alf P. Steinbach

* James Kanze:
[...]
So formally, if your interpretation is correct the only
rationale could be to support Really Really Extremely Phat
Pointers (RREPP) that on assignment checks the dynamic type of
the pointed to object and stores that type information, a
typeid-call for every pointer assignment.

I'm not sure how relevant it is here, but the authors of the
standard definitely did consider tagged architectures, where
each pointer also contains type information. (I believe the old
Burroughs processors had something along those lines. And of
course, more than one processor has used a tag to indicate
whether the pointer pointed to the final value, or to another
pointer.)

Yeah, thanks, I'm beginning to suspect that my initial statement(s) --
whatever they were, to lazy to look up back in the thread! :) --
were a bit too general...

However,

p = ::new(p) T()

with subsequent usage only through p, should handily beat tagged
architecture, RREPP and Kai-Uwe's suggestion, simply by updating the
external-to-object type information.

Note the original context, where the original code used delete+new, i.e.
new pointer value, i.e. having no other references to that object, and
the question was to replace that combination with destroy+construct.

Cheers,

- Alf
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top