Avi
Avi

Reputation: 1193

Parameter forwarding without temporary instances

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;

};

Upvotes: 3

Views: 252

Answers (4)

Micha&#235;l Roy
Micha&#235;l Roy

Reputation: 6494

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?

Upvotes: 1

balki
balki

Reputation: 27684

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

Upvotes: 3

Jarod42
Jarod42

Reputation: 218268

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)...);

Upvotes: 1

user7860670
user7860670

Reputation: 37607

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

Upvotes: 1

Related Questions