deleting dynamically allocated objects in a container

  • Thread starter subramanian100in
  • Start date
S

subramanian100in

Suppose 'Test' is a class. I dynamically allocate few 'Test' objects
and push them into a vector<Test*>. I want to delete the dynamically
allocated 'Test' objects using a Standard Library Algorithm instead of
writing a hand-written 'for loop'.

Following is my attempt: (I am using cout statements in the ctor and
dtor only for understanding purpose).

#include <cstdlib>
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Test
{
public:
explicit Test(int arg = 0);
~Test();
private:
int val;
};

inline Test::Test(int arg) : val(arg)
{
cout << "From Test ctor: " << val << endl;
}

inline Test::~Test()
{
cout << "From Test dtor: " << val << endl;
}

inline void delete_pointer(Test* & arg)
{
delete arg;
arg = 0;
}

int main()
{
typedef vector<Test*> Container;
Container c;
c.push_back(new Test(100));
c.push_back(new Test(200));
c.push_back(new Test(300));
for_each(c.begin(), c.end(), delete_pointer);
// just to ensure the element values are zero, print them
cout << c[0] << " " << c[1] << " " << c[2] << endl;

return EXIT_SUCCESS;
}

This porgram compiles fine with g++ and when run, produces the output:
From Test ctor: 100
From Test ctor: 200
From Test ctor: 300
From Test dtor: 100
From Test dtor: 200
From Test dtor: 300
0 0 0

My solution seems to work. But is there a better solution ?

My question is: in real-world applications, how do the dynamically
allocated objects stored in a container deleted ? Kindly provide the
code.

Thanks
V.Subramanian
 
J

James Kanze

Suppose 'Test' is a class. I dynamically allocate few 'Test'
objects and push them into a vector<Test*>. I want to delete
the dynamically allocated 'Test' objects using a Standard
Library Algorithm instead of writing a hand-written 'for
loop'.
Following is my attempt: (I am using cout statements in the
ctor and dtor only for understanding purpose).
#include <cstdlib>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Test
{
public:
explicit Test(int arg = 0);
~Test();
private:
int val;
};
inline Test::Test(int arg) : val(arg)
{
cout << "From Test ctor: " << val << endl;
}
inline Test::~Test()
{
cout << "From Test dtor: " << val << endl;
}

inline void delete_pointer(Test* & arg)
{
delete arg;
arg = 0;

Purely formally, the above results in undefined behavior. (In
practice, I wouldn't worry about it.)
}

int main()
{
typedef vector<Test*> Container;
Container c;
c.push_back(new Test(100));
c.push_back(new Test(200));
c.push_back(new Test(300));
for_each(c.begin(), c.end(), delete_pointer);
// just to ensure the element values are zero, print them
cout << c[0] << " " << c[1] << " " << c[2] << endl;
return EXIT_SUCCESS;
}
This porgram compiles fine with g++ and when run, produces the output:
From Test ctor: 100
From Test ctor: 200
From Test ctor: 300
From Test dtor: 100
From Test dtor: 200
From Test dtor: 300
0 0 0
My solution seems to work. But is there a better solution ?

Better, in what sense? Just writing a simple, easily readable
loop is usually the best solution.
My question is: in real-world applications, how do the
dynamically allocated objects stored in a container deleted?

That depends on what their lifetime should be. Cases where
dynamically allocated objects in a container should all be
deleted at the same instant are pretty rare.
 
K

Kai-Uwe Bux

Leigh said:
I am have problems spotting the UB, care to elaborate further?

That's about the container containing invalid pointer values. That renders
them non-copyconstructible and non-assignable (since the required lvalue to
rvalue conversion is UB). Therefore, the container contains objects that do
not satisfy the conceptual requirements of the container.

That said, I think, even

int main ( void ) {
std::vector< int* > v ( 10 );
int * the_mighty_invalidator = new int ( 3 );
delete the_mighty_invalidator;
... UB from here on ...
}

has UB under a possible (although clearly not intended) interpretation of
the standard: conceptual requirements are phrased "per type". In the above
example, int* becomes non-assignable and non-copyconstructible. Thus, even
though the vector does not contain any of the bad values, the conceptual
requirements are not satisfied.

Just goes to show that requirements are very, _very_ hard to phrase. It also
goes to show that the standard has to be interpreted with a good measure of
common sense. Now, the first interpretation could actually be intended. It
is clear that a vector implementation will ruthlessly copy and move objects
around. If they happen to be invalid pointers, you would want the vector
operations UB if the underlying pointer assignments are UB.

Now, as for the question whether an lvalue to rvalue conversion of an
invalid pointer _should_ be UB, I have no opinion to offer. Also, the
interpretation seems a little strict. But the intend of [4.1/1] seems to be
that lvalue to rvalue conversions on invalid values are UB, e.g., the bit
about uninitialized values.


Best

Kai-Uwe Bux
 
K

Kai-Uwe Bux

Leigh said:
So you are saying to avoid UB you must instead write:

inline void delete_pointer(Test* & arg)
{
Test* temp = arg;
arg = 0;
delete temp;
}

I find this rather surprising given there is no opportunity in the
original example for an lvalue to rvalue conversion to take place before
the pointer is set to 0 (even if one considers multiple threads of
execution as mutexes should protect against it).

Well, it is a matter of the phrasing in the standard. As of n3092, C++0X
specifies conceptual requirements not for membership in a container but for
each container operation. In particular, when there is no operation on a
container, there cannot be a violation of conceptual requirements. I hope,
that will make the behavior defined.


Best

Kai-Uwe Bux
 
A

Alf P. Steinbach /Usenet

* Kai-Uwe Bux, on 09.08.2010 21:24:
That's about the container containing invalid pointer values. That renders
them non-copyconstructible and non-assignable (since the required lvalue to
rvalue conversion is UB). Therefore, the container contains objects that do
not satisfy the conceptual requirements of the container.

Perhaps that's why the OP sets the pointer to zero, which is not an invalid
pointer value: it's perfectly copyable.

I.e., there's no UB in the code shown.

However, the arg = 0 serves no useful purpose in the OP's setting, where the
objects are deleted on container destruction (no copying of that container).


Cheers & hth.,

- Alf
 
A

Alf P. Steinbach /Usenet

* Kai-Uwe Bux, on 09.08.2010 21:56:
Well, it is a matter of the phrasing in the standard. As of n3092, C++0X
specifies conceptual requirements not for membership in a container but for
each container operation. In particular, when there is no operation on a
container, there cannot be a violation of conceptual requirements. I hope,
that will make the behavior defined.

There is no UB.


Cheers & hth.,

- Alf
 
K

Kai-Uwe Bux

Alf said:
* Kai-Uwe Bux, on 09.08.2010 21:24:

Perhaps that's why the OP sets the pointer to zero, which is not an
invalid pointer value: it's perfectly copyable.

The OP sets the value to 0 _after_ it is deleted. At that point, the
container already _contained_ an invalid value and the program is in UB land
as per the conceptual requirements for _membership_ in a container. The way
the current standard is phrased (as opposed to C++0x) the conceptual
requirement is not bound to operations performed on the container. I agree
that is weird, but I can see the point of the interpretation. I also think
that C++0X will fix this by tying conceptual requirements to container
operations.
I.e., there's no UB in the code shown.

That's incorrect.

[...]


Best

Kai-Uwe Bux
 
K

Kai-Uwe Bux

Kai-Uwe Bux said:
The OP sets the value to 0 _after_ it is deleted. At that point, the
container already _contained_ an invalid value and the program is in UB
land as per the conceptual requirements for _membership_ in a container.
The way the current standard is phrased (as opposed to C++0x) the
conceptual requirement is not bound to operations performed on the
container. I agree that is weird, but I can see the point of the
interpretation. I also think that C++0X will fix this by tying conceptual
requirements to container operations.


That's incorrect.

Well, that's probably overstating the issue. Let's say you are using a more
optimistic interpretation of the current standard.


Best

Kai-Uwe Bux
 
A

Alf P. Steinbach /Usenet

* Kai-Uwe Bux, on 09.08.2010 22:27:
Well, that's probably overstating the issue. Let's say you are using a more
optimistic interpretation of the current standard.

Uhm, sorry, but that's bullshit.

There's absolutely no UB.

There are two main errors in your attempted interpretation (and James', if this
is what he alluded to). First, the requirement of copy-constructible and
assignable element type are type requirements, not value requirements. If a
particular instance of T turns out to not be copyable when a copy is attempted,
then you have some effect that depdends on the details, it can be UB in some
cases, but no such copying is attempted in the OP's code. Secondly, any
invariant of a type can be temporarily broken and most often has to be
temporarily broken during an internal operation; the invariant has to hold on
external entry to an operation in the type's interface, and on corresponding
exits, that's all (the thing about "external entry" is a bit difficult, yes, but
I trust that you get the main drift of this even if that issue is complex).


Cheers & hth.,

- Alf
 
S

subramanian100in

* "Daniel T. said:
"(e-mail address removed), India" <[email protected]>



wrote:
Suppose 'Test' is a class. I dynamically allocate few 'Test' objects
and push them into a vector<Test*>. I want to delete the dynamically
allocated 'Test' objects using a Standard Library Algorithm instead of
writing a hand-written 'for loop'.
Following is my attempt: (I am using cout statements in the ctor and
dtor only for understanding purpose).
#include <cstdlib>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Test
{
public:
explicit Test(int arg = 0);
~Test();
private:
int val;
};
inline Test::Test(int arg) : val(arg)
{
cout << "From Test ctor: " << val << endl;
}
inline Test::~Test()
{
cout << "From Test dtor: " << val << endl;
}
inline void delete_pointer(Test* & arg)
{
delete arg;
arg = 0;
}
int main()
{
typedef vector<Test*> Container;
Container c;
c.push_back(new Test(100));
c.push_back(new Test(200));
c.push_back(new Test(300));
for_each(c.begin(), c.end(), delete_pointer);
// just to ensure the element values are zero, print them
cout << c[0] << " " << c[1] << " " << c[2] << endl;
return EXIT_SUCCESS;
}
This porgram compiles fine with g++ and when run, produces the output:
From Test ctor: 100
From Test ctor: 200
From Test ctor: 300
From Test dtor: 100
From Test dtor: 200
From Test dtor: 300
0 0 0
My solution seems to work. But is there a better solution ?

Here is Stroustrup's solution (from TC++PL)

template<class T> T* delete_ptr(T* p) { delete p; return 0; }

void purge(deque<Shape*>& s)
{
transform(s.begin(), s.end() ,s.begin(), &delete_ptr);

}

I get compilation error for this 'transform' call. Here is the
complete modified program z.cpp:

#include <cstdlib>
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Test
{
public:
explicit Test(int arg = 0);
~Test();
private:
int val;
};

inline Test::Test(int arg) : val(arg)
{
cout << "From Test ctor: " << val << endl;
}

inline Test::~Test()
{
cout << "From Test dtor: " << val << endl;
}

template <typename T>
T* delete_ptr(T* p)
{
delete p;

return 0;
}

int main()
{
typedef vector<Test*> Container;
Container c;
c.push_back(new Test(100));
c.push_back(new Test(200));
c.push_back(new Test(300));
transform(c.begin(), c.end(), c.begin(), &delete_ptr);
// just to ensure the element values are zero, print them
cout << c[0] << " " << c[1] << " " << c[2] << endl;

return EXIT_SUCCESS;
}

I compiled this program with g++3.4.3 as
g++ -std=c++98 -pedantic -Wall -Wextra z.cpp

It generated the following compilation error:

z.cpp: In function `int main()':
z.cpp:42: error: no matching function for call to
`transform(__gnu_cxx::__normal_iterator<Test**, std::vector<Test*,
std::allocator<Test*> > >, __gnu_cxx::__normal_iterator<Test**,
std::vector<Test*, std::allocator<Test*> > >,
__gnu_cxx::__normal_iterator<Test**, std::vector<Test*,
std::allocator<Test*> > >, <unknown type>)'

Kindly help me to make this modified program compile fine and produce
the expected result which I stated at the beginning of OP.

Thanks
V.Subramanian
 
J

James Kanze

* Kai-Uwe Bux, on 09.08.2010 21:24:

[...]
Perhaps that's why the OP sets the pointer to zero, which is
not an invalid pointer value: it's perfectly copyable.

Yes. But he deletes it before setting it to zero. So there is
a moment when the container contains an object which cannot be
copied without undefined behavior. In practice, I don't see any
way the above could actually fail; the standard says its UB if
the container contains an invalid pointer, but in practice, said
UB can only really occur if you do something with the container,
and in fact, will only occur if you do something that causes the
container to need to read the invalid pointer (e.g. extending
the size of the vector).
I.e., there's no UB in the code shown.
However, the arg = 0 serves no useful purpose in the OP's
setting, where the objects are deleted on container
destruction (no copying of that container).

Calling the destructor on a vector which contains invalid values
*is* UB, clearly. It's doing something with the container. But
again, in practice, it will work, because the destructor will
not read the values, just for the fun of it. (It's allowed to,
but no reasonable implementation will do something if there's no
reason to do it.)
 
A

Alf P. Steinbach /Usenet

* James Kanze, on 10.08.2010 13:07:
* Kai-Uwe Bux, on 09.08.2010 21:24:
[...]
Following is my attempt: (I am using cout statements in the
ctor and dtor only for understanding purpose).
#include<cstdlib>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
class Test
{
public:
explicit Test(int arg = 0);
~Test();
private:
int val;
};
inline Test::Test(int arg) : val(arg)
{
cout<< "From Test ctor:"<< val<< endl;
}
inline Test::~Test()
{
cout<< "From Test dtor:"<< val<< endl;
}
inline void delete_pointer(Test*& arg)
{
delete arg;
arg = 0;
Purely formally, the above results in undefined behavior. (In
practice, I wouldn't worry about it.)
I am have problems spotting the UB, care to elaborate further?
That's about the container containing invalid pointer
values. That renders them non-copyconstructible and
non-assignable (since the required lvalue to rvalue
conversion is UB). Therefore, the container contains objects
that do not satisfy the conceptual requirements of the
container.
Perhaps that's why the OP sets the pointer to zero, which is
not an invalid pointer value: it's perfectly copyable.

Yes. But he deletes it before setting it to zero. So there is
a moment when the container contains an object which cannot be
copied without undefined behavior. In practice, I don't see any
way the above could actually fail; the standard says its UB if
the container contains an invalid pointer,

No, it doesn't.

Just to be clear, §23.1/3 places a requirement on element /types/, that they be
copy constructible and assignable, and the word "type" is explicitly used.

It does not place a requirement on the instances of those types. Such instance
requirements are only implicitly present as requirements for operations. With no
operations, no implicit requirements either.

but in practice, said
UB can only really occur if you do something with the container,
and in fact, will only occur if you do something that causes the
container to need to read the invalid pointer (e.g. extending
the size of the vector).



Calling the destructor on a vector which contains invalid values
*is* UB, clearly.

Yes, since a simplistic implementation may do a (pseudo) destructor call on each
element, which causes evaluation.

Uhm, I was too fast there, you're right about the zeroing being formally required.

But then, as you note below, even a pseudo destructor call, if issued on a
pointer type element, will in practice do nothing, not even evaluation (although
the standard's formal effect is evaluation).

It's doing something with the container. But
again, in practice, it will work, because the destructor will
not read the values, just for the fun of it. (It's allowed to,
but no reasonable implementation will do something if there's no
reason to do it.)


Cheers & hth.,

- Alf
 
K

Kai-Uwe Bux

Alf said:
* James Kanze, on 10.08.2010 13:07:
* Kai-Uwe Bux, on 09.08.2010 21:24:
[...]
Following is my attempt: (I am using cout statements in the
ctor and dtor only for understanding purpose).
#include<cstdlib>
#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;
class Test
{
public:
explicit Test(int arg = 0);
~Test();
private:
int val;
};
inline Test::Test(int arg) : val(arg)
{
cout<< "From Test ctor:"<< val<< endl;
}
inline Test::~Test()
{
cout<< "From Test dtor:"<< val<< endl;
}
inline void delete_pointer(Test*& arg)
{
delete arg;
arg = 0;
Purely formally, the above results in undefined behavior. (In
practice, I wouldn't worry about it.)
I am have problems spotting the UB, care to elaborate further?
That's about the container containing invalid pointer
values. That renders them non-copyconstructible and
non-assignable (since the required lvalue to rvalue
conversion is UB). Therefore, the container contains objects
that do not satisfy the conceptual requirements of the
container.
Perhaps that's why the OP sets the pointer to zero, which is
not an invalid pointer value: it's perfectly copyable.

Yes. But he deletes it before setting it to zero. So there is
a moment when the container contains an object which cannot be
copied without undefined behavior. In practice, I don't see any
way the above could actually fail; the standard says its UB if
the container contains an invalid pointer,

No, it doesn't.

Just to be clear, §23.1/3 places a requirement on element /types/, that
they be copy constructible and assignable, and the word "type" is
explicitly used.

It does not place a requirement on the instances of those types. Such
instance requirements are only implicitly present as requirements for
operations. With no operations, no implicit requirements either.

Ok, let's assume that: no operations, no requirements. However, I am just
curious: would the following implementation of vector<T>::eek:perator[] be
conforming:

template < typename T, typename A >
class vector {
...
value_type * __the_data_ptr; // stores the content
...
public:
...
reference operator[] ( size_type n ) {
// first do something to trigger lvalue to rvalue conversion:
__the_data_ptr[n];
// or enforce some copy construction
value_type value ( the_data_ptr[n] );
// then return:
return ( __the_data_ptr[n] );
}
...
};

So, would operator[] be allowed to trigger gratuitous copying? If so, at
least the sequence

delete v;
v = 0;

has a problem. (However, the code of the OP would not have that problem,
although the solution posted by Daniel elsethread
Here is Stroustrup's solution (from TC++PL)

template<class T> T* delete_ptr(T* p) { delete p; return 0; }

void purge(deque<Shape*>& s)
{
transform(s.begin(), s.end() ,s.begin(), &delete_ptr);
}

looks as though sufficiently stupid implementations of transform() and
vector<T>::iterator::eek:perator* could trigger the problem.)


[...]

Best

Kai-Uwe Bux

ps.: I also start wondering whether the assumption that invalid int* are
actually objects of type int* is well-founded. I.e.:

int* a = new int (3);
int* b = a;
delete a;
// does the lvalue b refer to an object of type int* ?

My reason to wonder is [4.1/1] which ought to imply that an lvalue to rvalue
conversion for b is UB. However, if b is an object of type int*, I don't see
how the wording would imply UB. If b is not an object of type int*, then the
issue about containers may not be about CopyConstructible at all: it might
be about a vector<T> whose elements are not objects of type T.
 
A

Alf P. Steinbach /Usenet

* Kai-Uwe Bux, on 10.08.2010 18:12:
Alf said:
* James Kanze, on 10.08.2010 13:07:
On Aug 9, 9:15 pm, "Alf P. Steinbach /Usenet"<alf.p.steinbach
(e-mail address removed)> wrote:
* Kai-Uwe Bux, on 09.08.2010 21:24:

[...]
Following is my attempt: (I am using cout statements in the
ctor and dtor only for understanding purpose).

#include<cstdlib>
#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

class Test
{
public:
explicit Test(int arg = 0);
~Test();
private:
int val;
};

inline Test::Test(int arg) : val(arg)
{
cout<< "From Test ctor:"<< val<< endl;
}

inline Test::~Test()
{
cout<< "From Test dtor:"<< val<< endl;
}

inline void delete_pointer(Test*& arg)
{
delete arg;
arg = 0;

Purely formally, the above results in undefined behavior. (In
practice, I wouldn't worry about it.)

I am have problems spotting the UB, care to elaborate further?

That's about the container containing invalid pointer
values. That renders them non-copyconstructible and
non-assignable (since the required lvalue to rvalue
conversion is UB). Therefore, the container contains objects
that do not satisfy the conceptual requirements of the
container.

Perhaps that's why the OP sets the pointer to zero, which is
not an invalid pointer value: it's perfectly copyable.

Yes. But he deletes it before setting it to zero. So there is
a moment when the container contains an object which cannot be
copied without undefined behavior. In practice, I don't see any
way the above could actually fail; the standard says its UB if
the container contains an invalid pointer,

No, it doesn't.

Just to be clear, §23.1/3 places a requirement on element /types/, that
they be copy constructible and assignable, and the word "type" is
explicitly used.

It does not place a requirement on the instances of those types. Such
instance requirements are only implicitly present as requirements for
operations. With no operations, no implicit requirements either.

Ok, let's assume that: no operations, no requirements. However, I am just
curious: would the following implementation of vector<T>::eek:perator[] be
conforming:

template< typename T, typename A>
class vector {
...
value_type * __the_data_ptr; // stores the content
...
public:
...
reference operator[] ( size_type n ) {
// first do something to trigger lvalue to rvalue conversion:
__the_data_ptr[n];
// or enforce some copy construction
value_type value ( the_data_ptr[n] );
// then return:
return ( __the_data_ptr[n] );
}
...
};

In general no, because [] shall have constant complexity, and with the above,
the complexity depends on the type T.

However you can always adjust the example to work around that.

And then it's pedantically-formally allowed, just as a 1 GiB size for 'bool' is
pedantically-formally allowed. Both means pedantically-formal UB, like

int main() { bool b; }

because it just might exhaust the resources available.

It's nothing to worry about.

When we talk about UB we don't consider such things, because it is in the end
about practical real problems. If an actually used compiler or standard library
implementation starts exhbiting behavior like that, then it might be in order to
submit a Defect Report. Until then, common sense rules. :)

So, would operator[] be allowed to trigger gratuitous copying? If so, at
least the sequence

delete v;
v = 0;

has a problem. (However, the code of the OP would not have that problem,
although the solution posted by Daniel elsethread
Here is Stroustrup's solution (from TC++PL)

template<class T> T* delete_ptr(T* p) { delete p; return 0; }

void purge(deque<Shape*>& s)
{
transform(s.begin(), s.end() ,s.begin(),&delete_ptr);
}

looks as though sufficiently stupid implementations of transform() and
vector<T>::iterator::eek:perator* could trigger the problem.)


Yeah, but Stroustrup makes the valid assumption that such a sufficiently stupid
implementation will simply not be used.

[...]

Best

Kai-Uwe Bux

ps.: I also start wondering whether the assumption that invalid int* are
actually objects of type int* is well-founded. I.e.:

int* a = new int (3);
int* b = a;
delete a;
// does the lvalue b refer to an object of type int* ?

My reason to wonder is [4.1/1] which ought to imply that an lvalue to rvalue
conversion for b is UB. However, if b is an object of type int*, I don't see
how the wording would imply UB. If b is not an object of type int*, then the
issue about containers may not be about CopyConstructible at all: it might
be about a vector<T> whose elements are not objects of type T.

Well the standard could have been more clear, yes. But I think in the end it's
just a question of practicality. Formal considerations can only go some way,
beyond a certain point treating the standard as a mathematically rigorous
document just yields absurdities, like, a pedantically-formally possible
implementation with 1 GiB 'bool' that would never be used in practice.


Cheers,

- Alf
 
K

Keith H Duggar

Alf said:
* Kai-Uwe Bux, on 10.08.2010 18:12:
Ok, let's assume that: no operations, no requirements. However, I am just
curious: would the following implementation of vector<T>::eek:perator[] be
conforming:
   template<  typename T, typename A>
   class vector {
     ...
     value_type * __the_data_ptr; // stores the content
     ...
   public:
     ...
     reference operator[] ( size_type n ) {
       // first do something to trigger lvalue to rvalue conversion:
       __the_data_ptr[n];
       // or enforce some copy construction
       value_type value ( the_data_ptr[n] );
       // then return:
       return ( __the_data_ptr[n] );
     }
     ...
   };

In general no, because [] shall have constant complexity, and with the above,
the complexity depends on the type T.

Wrong.

23.1

2 All of the complexity requirements in this clause are stated solely
in
terms of the number of operations on the contained objects.
[Example:
the copy constructor of type vector <vector<int> > has linear
complex-
ity, even though the complexity of copying each contained
vector<int>
is itself linear. ]

ie the contained type T has zip to do with the standard's
complexity requirements.

KHD
 
K

Kai-Uwe Bux

Alf said:
* Kai-Uwe Bux, on 10.08.2010 18:12:
Alf said:
* James Kanze, on 10.08.2010 13:07:
On Aug 9, 9:15 pm, "Alf P. Steinbach /Usenet"<alf.p.steinbach
(e-mail address removed)> wrote:
* Kai-Uwe Bux, on 09.08.2010 21:24:

[...]
Following is my attempt: (I am using cout statements in the
ctor and dtor only for understanding purpose).

#include<cstdlib>
#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

class Test
{
public:
explicit Test(int arg = 0);
~Test();
private:
int val;
};

inline Test::Test(int arg) : val(arg)
{
cout<< "From Test ctor:"<< val<< endl;
}

inline Test::~Test()
{
cout<< "From Test dtor:"<< val<< endl;
}

inline void delete_pointer(Test*& arg)
{
delete arg;
arg = 0;

Purely formally, the above results in undefined behavior. (In
practice, I wouldn't worry about it.)

I am have problems spotting the UB, care to elaborate further?

That's about the container containing invalid pointer
values. That renders them non-copyconstructible and
non-assignable (since the required lvalue to rvalue
conversion is UB). Therefore, the container contains objects
that do not satisfy the conceptual requirements of the
container.

Perhaps that's why the OP sets the pointer to zero, which is
not an invalid pointer value: it's perfectly copyable.

Yes. But he deletes it before setting it to zero. So there is
a moment when the container contains an object which cannot be
copied without undefined behavior. In practice, I don't see any
way the above could actually fail; the standard says its UB if
the container contains an invalid pointer,

No, it doesn't.

Just to be clear, §23.1/3 places a requirement on element /types/, that
they be copy constructible and assignable, and the word "type" is
explicitly used.

It does not place a requirement on the instances of those types. Such
instance requirements are only implicitly present as requirements for
operations. With no operations, no implicit requirements either.

Ok, let's assume that: no operations, no requirements. However, I am just
curious: would the following implementation of vector<T>::eek:perator[] be
conforming:

template< typename T, typename A>
class vector {
...
value_type * __the_data_ptr; // stores the content
...
public:
...
reference operator[] ( size_type n ) {
// first do something to trigger lvalue to rvalue conversion:
__the_data_ptr[n];
// or enforce some copy construction
value_type value ( the_data_ptr[n] );
// then return:
return ( __the_data_ptr[n] );
}
...
};

In general no, because [] shall have constant complexity, and with the
above, the complexity depends on the type T.

Hm, interesting point. I was under the impression that the requirements for
std::vector said:
However you can always adjust the example to work around that.
True.

And then it's pedantically-formally allowed, just as a 1 GiB size for
'bool' is pedantically-formally allowed. Both means pedantically-formal
UB, like

int main() { bool b; }

because it just might exhaust the resources available.

Well, there still is a difference between UB on the level of the abstract
machine and UB because of resource limitations.
It's nothing to worry about.

I know that, you know that, you even know that I know that. Nobody ever
claimed that there is a reason to worry.

I get the feeling, you got the discussion the wrong way. This _never_ was
intended as a suggestion to change the code. James stated in his first post
that it is "purely formally" undefined behavior (right or wrong in the
assessment of UB, he left no doubt that there is nothing to worry about from
a practical point of view). Now, you introduce "pedantically-formal" UB.
Well, so be it.

I considered the whole point of the interpretation as hinting toward
suboptimal wording of the standard not hinting toward bad coding of the OP
or anybody else.
When we talk about UB we don't consider such things, because it is in the
end about practical real problems. If an actually used compiler or
standard library implementation starts exhbiting behavior like that, then
it might be in order to submit a Defect Report. Until then, common sense
rules. :)

Then, maybe I should embark on the project: The C++ Compiler from Hell :)
So, would operator[] be allowed to trigger gratuitous copying? If so, at
least the sequence

delete v;
v = 0;

has a problem. (However, the code of the OP would not have that problem,
although the solution posted by Daniel elsethread
Here is Stroustrup's solution (from TC++PL)

template<class T> T* delete_ptr(T* p) { delete p; return 0; }

void purge(deque<Shape*>& s)
{
transform(s.begin(), s.end() ,s.begin(),&delete_ptr);
}

looks as though sufficiently stupid implementations of transform() and
vector<T>::iterator::eek:perator* could trigger the problem.)


Yeah, but Stroustrup makes the valid assumption that such a sufficiently
stupid implementation will simply not be used.


Again, this is more about the wording of the standard than the quality of
code.
[...]

Best

Kai-Uwe Bux

ps.: I also start wondering whether the assumption that invalid int* are
actually objects of type int* is well-founded. I.e.:

int* a = new int (3);
int* b = a;
delete a;
// does the lvalue b refer to an object of type int* ?

My reason to wonder is [4.1/1] which ought to imply that an lvalue to
rvalue conversion for b is UB. However, if b is an object of type int*, I
don't see how the wording would imply UB. If b is not an object of type
int*, then the issue about containers may not be about CopyConstructible
at all: it might
be about a vector<T> whose elements are not objects of type T.

Well the standard could have been more clear, yes. But I think in the end
it's just a question of practicality. Formal considerations can only go
some way, beyond a certain point treating the standard as a mathematically
rigorous document just yields absurdities, like, a pedantically-formally
possible implementation with 1 GiB 'bool' that would never be used in
practice.

Yes, the standard could be clearer. And, among other things, that one can
get into absurdities testifies to this.

So, (a) with a given standard, one has to use common sense to arrive at the
intent; and I don't think anybody ever stated that the intent of the
standard is to render the code UB. All that was stated is that the standard
_implies_ the code to be UB -- which you now agree to, though for completely
different reasons (after all, T* can also be 1G:).

But (b), one can also watch out for spots in the standard that can be
improved. I think, this issue shows one. And I also think that C++0x will
improve upon the current state of affairs.


Best

Kai-Uwe Bux
 
K

Kai-Uwe Bux

Leigh said:
Kai-Uwe Bux said:
template < typename T, typename A >
class vector {
...
value_type * __the_data_ptr; // stores the content
...
public:
...
reference operator[] ( size_type n ) {
// first do something to trigger lvalue to rvalue conversion:
__the_data_ptr[n];

What lvalue to rvalue conversion? "__the_data_ptr[n]" is an lvalue
surely?

Yeah, I screwed up that line.


Best

Kai-Uwe Bux
 
A

Alf P. Steinbach /Usenet

* Keith H Duggar, on 10.08.2010 21:47:
Alf said:
* Kai-Uwe Bux, on 10.08.2010 18:12:
Alf P. Steinbach /Usenet wrote:
* James Kanze, on 10.08.2010 13:07:
Yes. But he deletes it before setting it to zero. So there is
a moment when the container contains an object which cannot be
copied without undefined behavior. In practice, I don't see any
way the above could actually fail; the standard says its UB if
the container contains an invalid pointer,
No, it doesn't.
Just to be clear, §23.1/3 places a requirement on element /types/, that
they be copy constructible and assignable, and the word "type" is
explicitly used.
It does not place a requirement on the instances of those types. Such
instance requirements are only implicitly present as requirements for
operations. With no operations, no implicit requirements either.
Ok, let's assume that: no operations, no requirements. However, I am just
curious: would the following implementation of vector<T>::eek:perator[] be
conforming:
template< typename T, typename A>
class vector {
...
value_type * __the_data_ptr; // stores the content
...
public:
...
reference operator[] ( size_type n ) {
// first do something to trigger lvalue to rvalue conversion:
__the_data_ptr[n];
// or enforce some copy construction
value_type value ( the_data_ptr[n] );
// then return:
return ( __the_data_ptr[n] );
}
...
};

In general no, because [] shall have constant complexity, and with the above,
the complexity depends on the type T.

Wrong.

23.1

2 All of the complexity requirements in this clause are stated solely
in
terms of the number of operations on the contained objects.
[Example:
the copy constructor of type vector<vector<int> > has linear
complex-
ity, even though the complexity of copying each contained
vector<int>
is itself linear. ]

ie the contained type T has zip to do with the standard's
complexity requirements.

Good point.

So, pedantically-formally std::vector<T>::eek:perator[] can have complexity as for
the copy constructor of T. :)

It's getting silly.


Thx.,

- 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,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top