Home > Enterprise >  What's the most efficient way to emplace a new element in a vector?
What's the most efficient way to emplace a new element in a vector?

Time:04-29

If I want to add a new element into a vector there are couple of ways I can do it:

struct ZeroInitialisedType
{
int a, b, c, d, e, f;
}

std::vector my_vector;

ZeroInitialisedType foo;
foo.a = 7;
//....
my_vector.push_back(foo);
  1. Can the compiler make it so that instead of writing into the local variable and then copying into the vector memory at end() that it writes it directly into the vector memory?
  2. Does the fact that the end() pointer may change (if the vector is resized) influence whether or not it can optimise two writes to one?
  3. What's the most efficient way to add this element to the end of the vector and write values to it?

The last question has got me thinking a bit about modern C 's annoying quirks about value-initialization. For example:

my_vector.emplace_back(); // PUSH A NEW OBJECT AT THE END
ZeroInitialisedType& ref = my_vector.back();
ref.a = 7;
//....

The problem with the above is that it zero initialises the new object on emplace_back(), then I write into it again. The same problem goes with this:

my_vector.emplace_back(ZeroInitialisedType());
// or
my_vector.push_back(ZeroInitialisedType()); 

If the element type of the vector isn't zero initialised then I assume the most efficient way would be to emplace_back with zero arguments, get a reference and write into it. However when it's zero-initialised that's not the case and it becomes a problem.

Also, I'm aware that a hack could be to add a user-defined constructor to the class type, but that's not good at all to avoid zero-initialization.

CodePudding user response:

I would suggest using following for inserting a small trivial aggregate type for readability:

my_vector.push_back({
    .a = 7,
});

However, emplacing may technically more efficient:

my_vector.emplace_back(7);

CodePudding user response:

There are two independent questions related to your problem. The first one is whether an object of type ZeroInitialisedType can be constructed such that some of its members are not zero-initialized. I believe this is not possible according to the aggregate-initialization rules. A simple demo is:

struct X { int a, b, c, d, e, f; };

void f(X* ptr)
{
    new (ptr) X{7};  // the same with X(7) and X{.a=7}
}

Here, all members b to f are zero-initialized in f().

You can avoid this zeroing by adding the corresponding constructor; however, the class will no longer be an aggregate:

struct Y
{
    Y(int a_) : a(a_) { }
    int a, b, c, d, e, f; 
};

void f(Y* ptr)
{
    new (ptr) Y(7);
}

Live demo: https://godbolt.org/z/3aWooxWK7.

The second question is whether the object can be constructed directly in the vector's buffer. I tried some versions with libstdc and only the version with "empty" emplace_back plus the back call did that.

Live demo: https://godbolt.org/z/zavvrTGj6.

I guess the cause is in the possible reallocation, which is in our case accomplished by the _M_realloc_insert call. It seems that those calls with non-empty template argument require the source to be in memory (the address is then passed via RDX). Therefore, the addressed objects need to be first stored to the stack.

The version with empty emplace_back does not have such requirements and succeeded with the direct construction in the vector's buffer. Of course, this analysis is GCC libstdc -related only.

  •  Tags:  
  • c
  • Related