you can easily make a class to keep track of the allocation count.
the reason we don't know the length is because it has always been an implementation detail (afaik). the compiler knows the elements' alignment, and the abi will also affect how it is implemented.
for example, itanium 64 abi stores the cookie (element count) in the leading bytes of the allocation (specifically, non-POD), then pads to the objects' natural alignment if necessary. you are then returned (from new[]
) the address of the first usable element, rather than the address of the actual allocation. so there is a bunch of non-portable bookkeeping involved.
a wrapper class is the easy way to manage this.
it's actually an interesting exercise to write allocators, override object::new/delete, placement operators and look at how this all fits together (although it's not a particularly trivial exercise if you want the allocator to be used in production code).
in short, we don't know the size of the memory allocation, and it is more effort to figure out the allocation size (among other necessary variables) consistently across multiple platforms than it is to use a custom template class which holds a pointer and a size_t
.
furthermore, there is no guarantee that the allocator allocated exactly the number of bytes requested (so your counts could be wrong, if you determine count based on allocation size). if you go through malloc interfaces, you should be able to locate your allocation... but that's still not very useful, portable, or safe for any non-trivial case.
Update:
@Default there are many reasons to create your own interface. as Tony mentioned, std::vector
is one well known implementation. the basis for such a wrapper is simple (interface borrowed from std::vector
:
/* holds an array of @a TValue objects which are created at construction and destroyed at destruction. interface borrows bits from std::vector */
template<typename TValue>
class t_array {
t_array(const t_array&); // prohibited
t_array operator=(const t_array&); // prohibited
typedef t_array<TValue>This;
public:
typedef TValue value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type* const pointer_const;
typedef const value_type* const const_pointer_const;
typedef value_type& reference;
typedef const value_type& const_reference;
/** creates @a count objects, using the default ctor */
t_array(const size_t& count) : d_objects(new value_type[count]), d_count(count) {
assert(this->d_objects);
assert(this->d_count);
}
/** this owns @a objects */
t_array(pointer_const objects, const size_t& count) : d_objects(objects), d_count(count) {
assert(this->d_objects);
assert(this->d_count);
}
~ t_array() {
delete[] this->d_objects;
}
const size_t& size() const {
return this->d_count;
}
bool empty() const {
return 0 == this->size();
}
/* element access */
reference at(const size_t& idx) {
assert(idx < this->size());
return this->d_objects[idx];
}
const_reference at(const size_t& idx) const {
assert(idx < this->size());
return this->d_objects[idx];
}
reference operator[](const size_t& idx) {
assert(idx < this->size());
return this->d_objects[idx];
}
const_reference operator[](const size_t& idx) const {
assert(idx < this->size());
return this->d_objects[idx];
}
pointer data() {
return this->d_objects;
}
const_pointer data() const {
return this->d_objects;
}
private:
pointer_const d_objects;
const size_t d_count;
};
as useful as std::vector
is, there are some cases where it can be useful to create your own bases:
- to make an object with a smaller interface. minimalism is good.
- to make an object which requires no allocator. for example:
t_array
will result in fewer exported symbols, as well as shorter names for those symbols (by removing the allocator argument).
- to make a variants which handle additional const cases. in the example above, there is often little reason to change what the container points to. so the above
t_array
uses 2 const members, each ensure less variation than std::vector
. a good optimizer should make use of those details. it also prevents users from making accidental mistakes.
- to reduce build times. if your needs are as simple as
t_array
, or even more simple then you can reduce your build times by using a minimal interface.
other cases:
- to make an object with a larger interface, or more features
- to make an object with additional debugging facilities
- to make an object which may be subclassed (most implementations of
std::vector
are not intended to be subclassed)
- to make an object which is thread safe