3
votes

I have a templated class which wraps a std::vector called mObjects.

Class has an insert function which forwards parameters of the actually stored type in the MyArray instance (its called variadic template arguments I think). In this example, I store MyMesh type, but it can be any kind of type.

As you can see in the main() function, mObjects vector doesn't grow, its elements get overwritten over and over again.

Think about an object-pool kind of data structure.

Everything works as expected.

template <class T>
class MyArray
{
    public:

        MyArray(const int capacity) :
            mObjects(capacity)
        {
            mNextIndex = 0;
        }

        template <typename... Args>
        void insert(Args&&... args)
        {

            mObjects[mNextIndex] = T{ std::forward<Args>(args)... };    //PROBLEMATIC ASSIGNMENT

            //... not relevant code
        }

    private:

        int             mNextIndex;
        std::vector<T>  mObjects;

};



int main()
{
    MyArray<Mesh> sa(2);

    sa.insert("foo",1111);  //goes to mObjects[0]
    sa.insert("bar",2222);  //goes to mObjects[1], and tada, the vector is full

    sa.remove(1111);    //not implemented above, but not relevant. Remove func basically adjusts mNextIndex, so mObjects[0] will be overwritten upon next insert.

    sa.insert("xxx",3333);  //mObjects[0] gets overwritten from "foo" to "xxx" and 1111 to 3333
}

My problem is with one row above commented as //PROBLEMATIC ASSIGNMENT.

mObjects[mNextIndex] = T{ std::forward<Args>(args)... };

When that command executes 3 things happen:

  1. MyMesh(const string s, int x) constructor is called, meaning an entire MyMesh gets allocated on stack here. Why? I just want to pass the forwarded arguments to an existing mObjects[mNextIndex] element.

  2. operator=(MyMesh&& other) is called, and does assignment variable by variable between the temporary variable and mObjects[mNextIndex].

  3. ~cVMesh() is called meaning the temporary variable deallocates and dies.

I would like to get rid of #1 and #3. So don't want the "expensive" temporary object creation. I just wish to forward/assign the incoming MyMesh parameters to mObjects[mNextIndex]. Similarly like what std::vector.emplace_back() does, but to any location pointed by mNextIndex.

How can I forward only the parameters to an existing variable in C++, without instantiate temporary variables?

For completness, here is the MyMesh class which gets stored in the MyArray class. Nothing special just printing out some message, when constructor/destructor/assignement operator is called:

class Mesh
{
public:
    Mesh()
    {
        cout << "Mesh()" << std::endl;
        mStr = "";
        mId = 99999999;
    }
    Mesh(const string s, int x)
    {
        cout << "Mesh(const string s, int x)" << std::endl;
        mStr = s;
        mId = x;
    }
    ~Mesh()
    {
        cout << "~Mesh()" << std::endl;
    }
    Mesh& operator=(const Mesh& other)
    {
        cout << "operator=(const Mesh& other)" << std::endl;
        cout << mStr << " becomes " << other.mStr << endl;
        cout << mId << " becomes " << other.mId << endl;
        mStr = other.mStr;
        mId = other.mId;
        return *this;
    }
    Mesh& operator=(Mesh&& other) noexcept
    {
        cout << "operator=(Mesh&& other)" << std::endl;
        cout << mStr << " becomes " << other.mStr << endl;
        cout << mId << " becomes " << other.mId << endl;
        mStr = other.mStr;
        mId = other.mId;
        return *this;
    }
    Mesh(const Mesh& other)
    {
        cout << "Mesh(const Mesh& other)" << std::endl;
        mStr = other.mStr;
        mId= other.mId;
    }
    Mesh(Mesh&& other) noexcept
    {
        cout << "Mesh(Mesh&& other)" << std::endl;
        mStr = other.mStr;
        mId = other.mId;
        other.mStr = "";
        other.mId = 99999999;
    }

    string mStr;
    int mId;

};
4
mObjects.emplace_back(std::forward<Args>(args)...). else your Mesh class has to implement an (variadic template) assign method.Jarod42
Yes, I wish emplace_back functionality, but to Nth place in the middle, or front or any random location. mObjects[mNextIndex]. mNextIndex can be anything really.Avi
@Avithohol: "I would like to get rid of #1 and #3." You can't, because there has to be an object there to copy from. #2 makes no sense if there's no object to provide the source data. Remember: there's already a live, constructed object. You can construct an object in-situ by first destroying it and re-constructing it, but that's rather silly just to avoid a temporary.Nicol Bolas

4 Answers

3
votes

I think what you want is to reconstruct an arbitary element in the vector with new values

#include<vector>

template<class T, class... Args>
void create_at_nth_place(std::vector<T>& v, int n, Args&&...args){
    auto& elem = v[n];
    elem.~T();
    new(&elem) T(std::forward<Args>(args)...);
}

struct S {
    S();
    S(int, bool);
    template<class... Args>
    S(Args&&...);
    S(S&&) noexcept;
    ~S();
};

void f() {
    std::vector<S> v(3);
    create_at_nth_place(v, 2, 4323, false);
    char a = 'a';
    create_at_nth_place(v, 2, 'a', 123, 1232, 32.f, a);
}

Link: https://godbolt.org/g/3K9akZ

1
votes

mObjects[mNextIndex] = T{ std::forward<Args>(args)... }; line creates a temporary object, performs a move(copy) assignment to object already stored in vector at specified position and finally destroys a temporary.

The whole MyArray class is rather useless since vector already has similar functionality.

vector<Mesh> sa;
sa.reserve(2);
sa.emplace_back("foo",1111); // Mesh constructor called once
sa.emplace_back("bar",2222); // Mesh constructor called once again
1
votes

You might add:

void assign(const string& s, int x)
{
    cout << "assign(const string s, int x)" << std::endl;
    mStr = s;
    mId = x;
}

And use it:

mObjects[mNextIndex].assign(std::forward<Args>(args)...);
1
votes

If your mesh class is very heavy, you should consider having an array of pointers, this would eliminate spurious copies altogether. Wouldn't MyArray<std::shared_ptr<MyMesh>> work as is?