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};
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 int
s 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:
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