Home > Enterprise >  c std vector initialize with existing objects
c std vector initialize with existing objects

Time:02-22

Below is some code that contains some already created Location objects and updates them. Then it needs to construct a std::vector of those objects to pass to other functions.

The way I construct the vector looks cleaner as it is a initializer list and is one line, instead of using 3 push_back calls after initializing an empty vector. Since we know all the elements that are going in to the vector already at construction time.

However, this leads to 2 copies being made per element. Firstly, why are there two copies being made in this line? Is the initializer list first constructor with copies, and then the vector constructor is called, therefore a second copy? std::vector<Location> pointsVec {l1, l2, l3};

And secondly, is there a vector constructor or another technique to initialize the vector with only 1 copy? (I want to make exactly 1 copy as I still want to use the local objects)

struct Location 
{
    Location(int x, int y, std::string frame)
    : x(x)
    , y(y)
    , frame(std::move(frame))
    {
        std::cout << "ctor" << std::endl;
    }

    Location(const Location & other)
    : x(other.x)
    , y(other.y)
    , frame(other.frame)
    {
        std::cout << "copy ctor" << std::endl;
    }
    
    Location(Location && other)
    : x(std::move(other.x))
    , y(std::move(other.y))
    , frame(std::move(other.frame))
    {
        std::cout << "move ctor" << std::endl;
    }

    int x;
    int y;
    std::string frame;
};

int main ()
{
    // local objects 
    Location l1 {1, 2, "local"};
    Location l2 {3, 4, "global"};
    Location l3 {5, 6, "local"};
    
    // code that updates l1, l2, l3
    // .
    // .
    // .

    // construct vector 
    std::vector<Location> pointsVec {l1, l2, l3}; // 2 copies per element 

    std::vector<Location> pointsVec1;
    pointsVec1.push_back(l1);
    pointsVec1.push_back(l2);
    pointsVec1.push_back(l3); // 1 copy per element 
    
    return 0;
}

edit: this question was in general for objects that are expensive to copy. adding a string to this struct to demonstrate that point

edit: adding sample move ctor

CodePudding user response:

Is there a vector constructor or another technique to initialize the vector with only 1 copy?

If you move the local objects into an array, you can construct the vector from that array, eg:

// local objects 
Location locs[3]{ {1, 2}, {3, 4}, {5, 6} };
    
// code that updates locs ...

// construct vector 
std::vector<Location> pointsVec {locs, locs 3};

Online Demo

Another option would be to simply get rid of the local objects altogether, construct them inside the vector to begin with, and then just refer to those elements, eg:

// construct vector 
std::vector<Location> pointsVec{ {1, 2}, {3, 4}, {5, 6} };

// local objects 
Location &l1 = pointsVec[0];
Location &l2 = pointsVec[1];
Location &l3 = pointsVec[2];
    
// code that updates l1, l2, l3 ...

CodePudding user response:

Initializer lists imply a copy. There's no way around this.

You can replace one of the two copies with a move, by writing {std::move(l1), std::move(l2), std::move(l3)} in the initializer. Note that, since your Location defines a custom copy constructor, it doesn't actually have a move constructor and will fall back to the copy.

If you want to avoid all copies, you instead have to move the elements into the vector one by one.

std::vector<Location> pointsVec;
pointsVec.reserve(3); // or else you get more copies/moves when the vector rellocates
pointsVec.push_back(std::move(l1));
pointsVec.push_back(std::move(l2));
pointsVec.push_back(std::move(l3));

But let's face it: your little class is 2 ints large. There is no real cost to the copies (and no advantage in moving over copying), and I mean that literally: chances are, the compiler will just optimize all of the copying away.

CodePudding user response:

If you want all three Location objects inside the vector, and you want them in L1, L2, L3, you definitely need to make a copy - three objects cannot be in six places, twice each.

If you don’t need the l1, l2, l3 instances in your local code afterwards, you could move them instead, by putting std::move() around them in the vector initialization. This is a big risk, however, because the variables seem to be still there and you might later add code that accesses them, with ugly consequences.

The better approach would be to construct them right in the vector initialization call instead of making temporary variables: {{1,2},{3,4},{5,6}};.

CodePudding user response:

For this case, you could use a combination of reserve, to avoid copies due to reallocation of the vector elements, and emplace_back, so you would end up with 3 new constructions, but no copies:

[Demo]

    std::vector<Location> pointsVec1;
    pointsVec1.reserve(3);
    pointsVec1.emplace_back(l1.x, l1.y);
    pointsVec1.emplace_back(l2.x, l2.y);
    pointsVec1.emplace_back(l3.x, l3.y); // 1 ctor per element 
  • Related