*sigh* well, personally I think better in code. I urge you all to compile/look at this and see what I mean. I'm sure it does a better job explaining than me.
Oh, as far as derived classes - you're quite right on that, it's not safe to copy any sort of type information. So, from here on, let's just assume that the templated class T is not derived. Ok? There are still plenty of non-POD (maybe I'm mistaken about the definition of POD) classes that are not derived.
#include <malloc.h>template<typename T>class custom_vector{public: /**Constructor. Does nothing but set the storage to uninitialized. */ custom_vector() : numObjects(0), maxObjects(0), storage(0) {} /**Destruction. Calls freeStorage(). */ ~custom_vector() { _freeStorage(); } /**In case the vector is, for instance, a pointer type, it may be easier *to use a member function to access elements. * @param index Specifies which element to return. * @return Returns the requested array element (non-constant). */ T& at(size_t index) { return *((T*)storage + index); } /**In case the vector is, for instance, a pointer type, it may be easier *to use a member function to access elements. * @param index Specifies which element to return. * @return Returns the requested array element (constant). */ const T& at(size_t index) const { return *((T*)storage + index); } /** @return Returns the requested array element (non-constant). */ T& operator[](size_t index) { return *((T*)storage + index); } /** @return Returns the requested array element (constant). */ const T& operator[](size_t index) const { return *((T*)storage + index); } /** @return Reports the number of objects in this vector. */ size_t size() const { return numObjects; } /**Adds a new element to the end of the list. *Note that this invokes a copy constructor. * @param object Object to add. */ void push_back(const T& object) { numObjects++; _checkStorage(); new((T*)storage + numObjects - 1) T(object); } /**Without parameters, push_back adds a default construction of the object. *This avoids a copy constructor, assuming default construction parameters *are acceptable. */ void push_back() { numObjects++; _checkStorage(); new((T*)storage + numObjects - 1) T(); } /**Removes and deletes the final element in storage. */ void pop_back() { numObjects--; ((T*)storage + numObjects)->~T(); } /**Exchanges two elements. This function is provided to help avoid copy *constructors (for a temporary object). * @param element1 First element to be exchanged * @param element2 Element to exchange with the first */ void exchange(size_t element1, size_t element2) { char exchangeSpace[sizeof(T)]; T* element1Ptr = (T*)storage + element1; T* element2Ptr = (T*)storage + element2; memcpy(exchangeSpace, element1Ptr, sizeof(T)); memcpy(element1Ptr, element2Ptr, sizeof(T)); memcpy(element2Ptr, exchangeSpace, sizeof(T)); } /**Adjusts the storage capacity to the requested size. If smaller than the *current array, objects will be deleted off of the end down to the new *size. If larger than existing array, new (default) objects will be *created to fill the new space. */ void resize(size_t newSize) { if (numObjects == newSize) return; else if (numObjects > newSize) { T* freeObject = (T*)storage + numObjects - 1; while (numObjects > newSize) { freeObject->~T(); freeObject--; numObjects--; } _resizeStorage(newSize); } else { //numObjects < newSize _resizeStorage(newSize); T* newObject = (T*)storage + numObjects; while (numObjects < newSize) { new(newObject++) T(); numObjects++; } } }private: /**Asserts that storage is large enough to contain all of our objects. *Note that functions adding to storage size call this after incrementing *numObjects. */ void _checkStorage() { if (numObjects > maxObjects) { if (!storage) { const size_t growth = 10; storage = (char*)malloc(sizeof(T) * growth); maxObjects = growth; } else { const size_t growth = numObjects + (numObjects >> 1) + 10; storage = (char*)realloc(storage, sizeof(T) * growth); maxObjects = growth; } } } /**Resizes the storage array without touching the object count. *Does update maxObjects, however. * @param count The new size of the storage array. */ void _resizeStorage(size_t count) { if (storage) { storage = (char*)realloc(storage, sizeof(T) * count); } else storage = (char*)malloc(sizeof(T) * count); maxObjects = count; } /**Frees the storage array. */ void _freeStorage() { for (size_t i = 0; i < numObjects; i++) { ((T*)storage + i)->~T(); } free(storage); } //Our actual data storage. Stored as a char* for easy addressing. char* storage; //Size of the storage size_t numObjects; //Maximum size of the storage size_t maxObjects;};
Then, for our std::string arguers:
#include <string>void main(){ custom_vector<std::string> myVec; for (int i = 0; i < 10000; i++) { myVec.push_back(std::vector("Hello bob. . . ")); } //put a break point if you want; they're all valid, separate //strings. Only 10000 constructors were called, and 10000 //copy constructors.}
Oh, and for a separate issue . . . is using size_t bad? I think it is, but not sure on that one. I grabbed it because it's well defined as the native unsigned integer type, unless, again, I'm mistaken.
-Walt