Search code examples
c++stdvectorplacement-new

Std::vector vs. placement new for communicating const array with size known at run-time


I have two methods for creating a list of objects at run-time which have potential downsides that I'd like to consider. Ultimately I am wondering which one more e

The criteria of my problem:

  • One major object will contain a fixed collection of minor objects (technically representing a 2d array)
  • Desire not to implement default constructor for the minor object since it will have some const paramter that gets assigned via user input (default may lead to invalid states elsewhere in the program!)
  • Very few (1 for most cases) of the major object will be needed per program execution
  • The state of each minor object will likely depend on some user input (likely in form of a config file).

My understanding:

  • Std::vector is likely more clear in how objects are created but I don't want to encourage changing the size of the array which I believe is a somewhat implied feature of any std::vector.
  • Placement new seems to successfully create pool of objects as desired while discouraging increasing or decreasing the size of the array since there is no default function for this.
  • I don't see any difference in downstream usage so leaning towards placement new despite the less frequent syntax since it discourages downstream resizing.

Does this reasoning align with the intention of placement new or am I missing a more appropriate standard container?

In the example below object list option 1 & 2 represent a std::vector based approach vs. placement new respectively. For simplicity I left out any exception handling. The ideal downstream usage (e.g. no resize) looks the same to me (access via [] operator).

For reference std::vector::emplace vs placement new might seem like a relevant question but I am trying to find an ideal solution for this particular set of criteria where the other question is considerably more vague. It is also possible that a different container or pattern would better solve this and that the two options here are simply a starting point.

#include <vector>

class minorObj{
    const int value;
    public:
    minorObj() = delete;
    minorObj(int x):value(x){}
};



class majorObj{
    const int N;
    public:
    std::vector<minorObj> objList_op1;
    minorObj*            objList_op2;
    majorObj(int size):N(size){
        objList_op1.reserve(N);
        objList_op2 = static_cast<minorObj*>(operator new[](N * sizeof(minorObj)));
        for(int i = 0; i<N;i++){
            objList_op1.emplace_back(minorObj(i));
            new(&objList_op2[i]) minorObj(i);
        }
    }
    ~majorObj(){
        for(int i = N -1; i >= 0; i--){
            objList_op2[i].~minorObj();
        }
    }
};

void someFn(){majorObj(25);} //added some different scope to emphasize need for proper memory mgmt

int main(){
    someFn();
    return 0;
}

Solution

  • Based on Quentin's comment above an example implementation of a wrapper class could be done with the following template class. This allows for run-time initialization benefits of std::vector while allowing/disallowing other operations as needed. Access to the objects it contains would be the same via the subscript operator.

    template <class T>
    class RuntimeArray{
        private:
        std::vector<T> values;
        public:
        RuntimeArray() = delete;
        RuntimeArray(const std::vector<int>& inputs){ // can be whatever type to initialize
            values.reserve(inputs.size());
            for(int i = 0; i < inputs.size(); i++){
                values.emplace_back(inputs[i]);
            }
        }
        T& operator [](int i){
            return values[i];   
        }
        const T& operator[](int i) const{
            return values[i];
        }
        // std::vector without std::vector::push_back or std::vector::erase etc.
    };