c ++ std向量用现有对象初始化

c++ std vector initialize with existing objects

下面是一些包含一些已创建的 Location 对象并更新它们的代码。然后它需要构建这些对象的 std::vector 以传递给其他函数。

我构建 vector 的方式看起来更清晰,因为它是一个初始化列表并且是一行,而不是在初始化空向量后使用 3 push_back 调用。因为我们在构造时就已经知道所有要进入向量的元素。

但是,这会导致每个元素制作 2 个副本。首先,为什么要在这一行中制作两个副本?初始化列表是第一个带有副本的构造函数,然后调用向量构造函数,因此是第二个副本吗? std::vector<Location> pointsVec {l1, l2, l3};

其次,是否有向量构造函数或其他技术来仅用一个副本初始化向量? (我想制作 1 个副本,因为我仍然想使用本地对象)

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;
}

编辑:这个问题一般是针对复制成本很高的对象。向该结构添加一个字符串以证明这一点

编辑:添加样本移动构造函数

初始化器列表意味着一个副本。没有办法解决这个问题。

您可以通过在初始值设定项中写入 {std::move(l1), std::move(l2), std::move(l3)} 来用移动替换两个副本之一。请注意,由于您的 Location 定义了自定义复制构造函数,因此它实际上没有移动构造函数,并且会回退到副本。

如果要避免所有副本,则必须将元素一个一个地移动到向量中。

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

但让我们面对现实吧:你的小 class 有 2 int 大。复制没有实际成本(并且移动复制没有优势),我的意思是字面意思:编译器可能会优化所有复制。

如果你想要向量中的所有三个 Location 对象,并且你想要它们在 L1L2L3 中,你肯定需要制作一个副本- 三个对象不能在六个地方,每个地方两次。

如果您之后在本地代码中不需要 l1l2l3 实例,您可以 移动 它们相反,通过在矢量初始化中将它们放在 std::move() 周围。然而,这是一个很大的风险,因为变量似乎仍然存在,您稍后可能会添加访问它们的代码,结果很糟糕。

更好的方法是在向量​​初始化调用中构造它们而不是创建临时变量: {{1,2},{3,4},{5,6}};.

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

如果将本地对象移动到数组中,则可以从该数组构造向量,例如:

// 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

另一种选择是完全摆脱局部对象,首先在 vector 中构建它们,然后只引用这些元素,例如:

// 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 ...

对于这种情况,您可以结合使用 reserveemplace_back 来避免由于向量元素的重新分配而导致的复制,因此您最终会得到 3 个新结构,但是没有副本:

[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