views:

210

answers:

8

I remember hearing that the following code is not C++ compliant and was hoping someone with much more C++ legalese than me would be able to confirm or deny it.

std::vector<int*> intList;
intList.push_back(new int(2));
intList.push_back(new int(10));
intList.push_back(new int(17));

for(std::vector<int*>::iterator i = intList.begin(); i != intList.end(); ++i) {
  delete *i;
}
intList.clear()

The rationale was that it is illegal for a vector to contain pointers to invalid memory. Now obviously my example will compile and it will even work on all compilers I know of, but is it standard compliant C++ or am I supposed to do the following, which I was told is in fact the standard compliant approach:

while(!intList.empty()) {
  int* element = intList.back();
  intList.pop_back();
  delete element;
}
+8  A: 

You code is valid, but the better solution will be to use smart pointers.

The thing is that all requirements to std::vector are located in 23.2.4 section of C++ Standard. There're no limitations about invalid pointers. std::vector works with int* as with any other type (we doesn't consider the case of vector<bool>), it doesn't care where they are point to.

Kirill V. Lyadvinsky
+1 for smart pointers.
Randolpho
The problem I have with smart pointers is both their semantics and their overhead. Storing twice as much information and having to perform atomic operations everything copies are made... I have no problem just deleting the elements myself, my concern is whether it is legal to do so.
Kranar
As I said your code is valid and standard compliant. As for smart pointers you could consider using `boost::ptr_vector`, maybe you will like it. Smart pointers gives you a more safe code, but it is case of taste.
Kirill V. Lyadvinsky
I understand and I'm glad you gave your opinion on it... but really I was hoping for a little more than just a yes or no. Some justification, or use cases or perhaps even just a reference to the standard that specifies that invalid pointers, despite not being allowed in ordinary usage, are allowed to be contained within a vector as a special case. Consider that copying a pointer to invalid memory is illegal, and consider that vectors do a lot of copying which would thus make that illegal. The question is do vectors allow for special cases?But anyhow, thanks for your input.
Kranar
the problem with not using smart pointers is when you don't have the opportunity to delete the elements yourself. perhaps less of an issue with int, but certainly important for other heavier types.
Greg Domjan
The thing is that all requirements to `std::vector` are located in 23.2.4 section of C++ Standard. There're no limitations about invalid pointers. `vector` works with `int*` as with any other type, it doesn't care where they are points to.
Kirill V. Lyadvinsky
@Kranar - "Consider that copying a pointer to invalid memory is illegal" - no, it's not. But dereferencing such a pointer of course is.
Pukku
I really have to object that vector works with int* as it does with any other type except for bool. vector's have requirements on the types they store, for example, they need to have copy constructors. My question on the surface seems very simple, deceptively so infact... but C++ is that kind of language, things that should be simple have very complex rules and exceptions to those rules, and I have reason to believe that vectors can not store pointers with invalid values.
Kranar
@Kranar, there is nothing in the C or C++ specifications that disallow invalid pointers, as long as you don't dereference them. You have a misconception.
Mark Ransom
@Kranar: I think in this case you're going to have to prove your point. Show in the C++ spec where vectors are denied the ability to store a pointer to invalid / unclaimed memory. I think you're wrong. There's *no such thing* as an "invalid value" for a pointer; the only time it'll be a problem is if it's dereferenced, in which case you'll either get bad data (if it just happens to point to an internal portion of a larger, valid data structure) or a segfault.
Randolpho
@Randolpho: Well if you have access to the spec you can just search for invalid pointer value. S 3.7.3.2 paragraph 4 specifies that deallocating a pointer renders invalid the value of that pointer. Any operations performed on an invalid pointer value is undefined.
Kranar
@Kranar: I don't know what spec you're using, but I'm using this one: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3092.pdf and there's Section 3.7.4.2 paragraph 4 which reads thus: "The effect of using an invalid pointer value (including passing it to a deallocation function) is undefined." That means that a pointer can have an invalid value, but if you try to *use* it (by dereferencing it) the effect is undefined. Storing an invalid pointer value is not, itself, undefined. Using it is. Therefore it's a best practice to avoid keeping them, but it's not "illegal".
Randolpho
@Kranar: (3.7.3.2.4) says: "The effect of using an invalidpointer value (including passing it to a deallocation function) is undefined.33)" And it is referring **specifically** to using the "deallocation function" on an invalid pointer value. The Standard says nothing about invalid pointers other that things like deleting them and calling through wild pointers.
John Dibling
@John It is not referring specifically to using the deallocation function. It is INCLUDING the deallocation function as one of the possible use-cases which results in undefined behavior when used on an invalid pointer value. Another possible use case could be assignment, copying, dereferencing, performing pointer arithmetic, or the myriads of other ways in which pointers can be used. Including a use case as an example does not imply restricting specifically to that one use case. The standard has much to say about invalid pointers, for example it's illegal to perform arithmetic on them.
Kranar
@Kranar: 3.7.3.2.4 is a sub-clause of 3.7.3.2, which defines "Deallocation functions." It only defines deallocation functions, and nothing else. 3.7.3.2.4 is referring specifically to the deallocation function, not including it in a wider statement.
John Dibling
@Kranar: If by pointer arithmetic you are referring to something like `(*p) += 4` of course that is UB, because you are dereferencing a wild pointer. But `p += 4` is perfectly valid and legal, so long as `P` is a complete type (eg, the size is known).
John Dibling
@John: 3.7.3.2.4 which as you correctly mention is about deallocation functions, is specifying specifically what the consequence of using such a deallocation function on a valid pointer value is. The consequence it is specifying is that if the valid pointer value being deallocated is non-null, then that pointer value no longer becomes valid, and hence, any use of said invalid pointer value results in undefined behavior. It does not mean simply that invalid pointer values can not be deallocated, it is making a general statement about the consequence of deallocating a valid pointer value.
Kranar
@John: 5.7 paragraph 5 specifies that unless the result of performing addition between a pointer and an integral type results in a pointer to the same array object or one past the last element of the array object, then the result is undefined. Hence p += 4 when p is an invalid pointer value results in undefined behavior even for a complete type. I'm telling you... this is a tricky language and it requires a very fine understanding to know whether something is valid or not. That's just an unfortunate consequence of C++.
Kranar
+1  A: 

Ultimately, this is a question of personal taste more than anything. It's not "standards non-compliant" to have a vector that contains invalid pointers, but it is dangerous, just like it's dangerous to have any pointer that points to invalid memory. Your latter example will ensure that your vector never contains a bad pointer, yes, so it's the safest choice.

But if you knew that the vector would never be used during your former example's loop (if the vector is locally scoped, for example), it's perfectly fine.

Randolpho
The issue is that it's invalid to copy a pointer that points to invalid memory. Now sure it will work on most compilers without a problem, but it is illegal C++. Considering that vector's do a lot of copying of their elements, it could be considered thus, invalid for a vector to store an invalid pointer.
Kranar
@Kranar: that's simply not true. C++ doesn't care what a pointer points to, and the language rules make no distinction. In fact, they give you lots of ways to shoot your toes off with pointer arithmetic. There are rules in the CPU and OS that keep a pointer out of addresses a program isn't allowed to use, but that's not C++, it's the system the program is operating in. C++ doesn't care. Yes, it's *Bad Practice*, and **Dangerous** to have invalid pointers, but it's not "illegal C++".
Randolpho
A: 

Where did you hear that? Consider this:

std::vector<int *> intList(5);

I just created a vector filled with 5 invalid pointers.

Niki Yoshiuchi
This is not true. Your vector will be filled with 5 pointers to NULL as vector will default initialize all its members.NULL pointers are indeed valid.
Kranar
Actually, NULL pointers are by definition guaranteed to be invalid. They are the only pointer values where you can actually validate their invalidity. I believe what you mean to say is that the vector is filled with *initialized* pointer values rather than *uninitialized* values.
Noah Roberts
A NULL pointer value is a valid pointer value. There are rules about what constitutes a valid pointer value and they are numerous. While dereferencing a NULL valued pointer results in undefined behavior, it is nevertheless a valid pointer value. An invalid pointer value would be something like a pointer to deallocated memory. No operations may be performed on such a pointer.
Kranar
@Kranar: There are *no* rules about what a NULL pointer is. NULL is a standardized value of 0, and *nothing else*. The rules you're talking about are not part of the language, they're "best practices" that have arisen *around* the language because of how easy it is to screw yourself if you're not careful when you use C or C++.
Randolpho
Of course there are rules about what a NULL valued pointer is. Furthermore a NULL valued pointer is not a standardized value of 0, the rule simply specifies that the integer constant 0 will be converted into a null valued pointer. However some systems use other values to represent a null valued pointer. As I said C++ is a very tricky language and it requires a very precise understanding of the language to be able to answer some of these corner cases.
Kranar
Furthermore, S 3.9.2 paragraph 3 specifies that a NULL valued pointer is infact a valid pointer value. It is quoted as thus: "A valid value of an object pointer type represents either the address of a byte in memory (1.7) or a null pointer(4.10)."
Kranar
+4  A: 

Your code is fine. If you're worried for some reason about the elements being invalid momentarily, then change the body of the loop to

int* tmp = 0;
swap (tmp, *i);
delete tmp;
John
A: 

In storing raw pointers in a container (I wouldn't recommend this) then having to do a 2 phase delete, I would choose your first option over the second.

I believe container::clear() will delete the contents of the map more efficiently than popping a single item at a time.

You could probably turn the for loop into a nice (psuedo) forall(begin(),end(),delete) and make it more generic so it didn't even matter if you changed from vector to some other container.

Greg Domjan
+2  A: 

The C++ philosophy is to allow the programmer as much latitude as possible, and to only ban things that are actually going to cause harm. Invalid pointers do no harm in themselves, and therefore you can have them around freely. What will cause harm is using the pointer in any way, and that therefore invokes undefined behavior.

David Thornley
"only ban things that are actually going to cause harm" I think it's important to point out that the Standard tends to ban things that cause harm *to the implementation*, rather than to the programs dumb programmers write.
John Dibling
A: 

I don't believe this is an issue of standards compliance. The C++ standards define the syntax of the language and implementation requirements. You are using the STL which is a powerful library, but like all libraries it is not part of C++ itself...although I guess it could be argued that when used aggressively, libraries like STL and Qt extend the language into a different superset language.

Invalid pointers are perfectly compliant with the C++ standards, the computer just won't like it when you dereference them.

What you are asking is more of a best practices question. If your code is multi-threaded and intList is potentially shared, then your first approach may be more dangerous, but as Greg suggested if you know that intList can't be accessed then the first approach may be more efficient. That said, I believe safety should usually win in a trade-off until you know there is a performance problem.

As suggested by the Design by Contract concept, all code defines a contract whether implicit or explicit. The real issue with code like this is what are you promising the user: preconditions, postconditions, invariants, etc. The libraries make a certain contract and each function you write defines its own contract. You just need to pick the appropriate balance for you code, and as long as you make it clear to the user (or yourself six months from now) what is safe and what isn't, it will be okay.

If there are best practices documented with with an API, then use them whenever possible. They probably are best practices for a reason. But remember, a best practice may be in the eye of the beholder...that is they may not be a best practice in all situations.

Chris Morlier
A: 

it is illegal for a vector to contain pointers to invalid memory

This is what the Standard has to say about the contents of a container:

(23.3) : The type of objects stored in these components must meet the requirements of CopyConstructible types (20.1.3), and the additional requirements of Assignable types.

(20.1.3.1, CopyConstructible) : In the following Table 30, T is a type to be supplied by a C + + program instantiating a template, t is a value of type T, and u is a value of type const T.

expression  return type  requirement
xxxxxxxxxx    xxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
T(t)                       t is equivelant to T(t)
T(u)                       u is equivelant to T(u)
t.~T()      
&t          T*           denotes the address of t
&u          const T*     denotes the address of u

(23.1.4, Assignable) : 64, T is the type used to instantiate the container, t is a value of T, and u is a value of (possibly const) T.

expression  return type  requirement
xxxxxxxxxx    xxxxxxxxxxx  xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
t = u         T&           t is equivilant to u

That's all that is says about the contents of an STL collection. It says nothing about pointers and it is particularly silent about the pointers pointing to valid memory.

Therefore, deleteing pointers in a vector, while most likely a very bad architectural decision and an invitation to pain and suffering with the debugger at 3:00 AM on a Saturday night, is perfectly legal.

EDIT:

Regarding Kranar's comment that "assigning a pointer to an invalid pointer value results in undefined behavior." No, this is incorrect. This code is perfectly valid:

Foo* foo = new Foo();
delete foo;
Foo* foo_2 = foo;  // This is legal

What is illegal is trying to do something with that pointer (or foo, for that matter):

delete foo_2; // UB
foo_2->do_something(); // UB
Foo& foo_ref = *foo_2; // UB

Simply creating a wild pointer is legal according to the Standard. Probably not a good idea, but legal nonetheless.

EDIT2:

More from the Standard regarding pointer types.

So sayeth the Standard (3.9.2.3) :

... A valid value of an object pointer type represents either the address of a byte in memory (1.7) or a null pointer (4.10)...

...and regarding "a byte in memory," (1.7.1) :

The fundamental storage unit in the C + + memory model is the byte. A byte is at least large enough to contain any member of the basic execution character set and is composed of a contiguous sequence of bits, the number of which is implementation-defined. The least significant bit is called the low-order bit; the most significant bit is called the high-order bit. The memory available to a C + + program consists of one or more sequences of contiguous bytes. Every byte has a unique address.

There is nothing here about that byte being part of a living Foo, about you having access to it, or anything of the sort. Its just a byte in memory.

John Dibling
Thank you, consider however that assigning a pointer to an invalid pointer value results in undefined behavior. Would this not then make it illegal to store an invalid pointer value in a vector, since doing so violates the assignable requirement?
Kranar
@Kranar: I will address this in an edit of my post in a moment.
John Dibling
@Kranar: assigning a pointer to an invalid memory address does not result in undefined behavior. I think that's the problem you're having. It's perefectly legal to do, for example, `int * p = reinterpret_cast<int *>(0x34E483);`, inserting whatever value I want to create a pointer to an arbitrary address. That's *legal* C++. And that pointer now points to an invalid memory address. The only behavior that is undefined is if I later on do, for example, `*p = 12;`.
Randolpho
Well I guess that's the crux of the matter... from what I know the code you provided is undefined behaviour. Now, it is possible on a given implementation that the address 0x34E483 will get converted into something valid by the compiler... for example it gets converted into the address of your sound card or video card... however, without such a guarantee from your compiler it is undefined behavior. Undefined behavior, after all can be defined for a specific platform or compiler implementation.
Kranar
@Kranar: If you are `delete` ing pointers in a `vector` that you allocated with `new`, then it is an address in memory. How can it be other?
John Dibling
@John: I don't understand, I'm not arguing that something allocated by new refers to the address of the object in memory. I'm arguing that operations performed using an invalid pointer value results in undefined behavior, and as such, since a vector may perform operations in the background such as assignment or copying, that doing so on an invalid pointer value implies that such behavior is also undefined.
Kranar
John Dibling
@John I disagree. You may not assign an invalid pointer value. You may store an invalid pointer value because you just used delete... but you may not use such an invalid value in any operation, be it copying, assigning, dereferencing, arithmetic, anything... since foo is an invalid pointer value, using it in an assignment results in undefined behavior, granted that most compilers won't complain and most runtimes won't care either.
Kranar
@Kranar: Then we're going to have to agree to disagree. I wonder why you posted this thread int he first place, given that you are so certian that my code above is illegal.
John Dibling
@John Dibling: I'm beginning to believe it's a (very successful) troll.
Randolpho
@Randolpho: Maybe, but I give @Kranar the benefit of the doubt and attribute this to his innocent (albiet stubbornly unyielding) misinterpretation of the Standard.
John Dibling
A vector of pointers can most certainly contain pointers to invalid memory, with absolutely legal and defined behavior. There is nothing undefined about it. *dereferencing* said points is what leads to the undefined behavior. Otherwise, you'd never be able to define a vector that has `null` as a value. And if that was truly the case, why have a vector of pointers.
Nathan Ernst
Anyone is free to think I'm a troll or whatnot and feel free to discontinue discussion. I upvoted this post because I think it contributes. But if anytime you disagree with someone who makes points and backs those points up with references you claim he's a troll then that's your prerogative. The fact is that no one has responded to my claim, with references to the standard, that performing operations on invalid pointer values is undefined behavior. Copying and assigning constitute operations... I was kind of hoping we'd have actual experts on C++ available who could reference otherwise.
Kranar
@Nathan: Null values are valid pointer values, the standard explicitly specifies this in S 3.9.2 paragraph 3.
Kranar