是否有任何方法可以将在堆栈内存上的函数中创建的对象的所有权传递给函数,而不使用复制构造?
通常,编译器会自动调用对函数堆栈上的对象的破坏。因此,如果我们想要创建一个类的对象(可能带有一些特定的参数),我们如何避免浪费大量资源从临时对象复制?
以下是一个常见案例:
while(...){
vectors.push_back(createObject( parameter ));
}
因此,当我们想要在迭代中使用某些参数创建对象并将它们推入vector
时,正常值传递将花费大量时间复制对象。 我不想在堆内存上使用指针和new
对象,因为用户可能会忘记delete
它们并因此导致内存泄漏。
嗯,智能指针可能是一个解决方案。但是......我认为......无论如何。 HHHH
有没有办法应用rvalue reference
和move
语义来解决这个问题?
答案 0 :(得分:4)
通常,按值返回对象将不复制对象,因为编译器应执行(命名)返回值优化,从而忽略副本。
通过此优化,返回对象的空间从调用上下文(外部堆栈帧)分配,对象直接在那里构建。
在您的示例中,编译器将在调用createObject()
的上下文中为对象分配空间。由于此上下文是std::vector<T>.push_back()
成员函数的(未命名)参数,因此它将作为右值引用,因此push_back()
by-value将通过将(而不是复制)它移动到向量。这是可能的,因为如果生成的对象是可移动的。否则,将出现副本。
总之,每个对象都将被创建,然后移动(如果可移动)到向量中。
以下是一个示例代码,更详细地说明了这一点:
#include <iostream>
#include <string>
#include <vector>
using Params = std::vector<std::string>;
class Object
{
public:
Object() = default;
Object(const std::string& s) : s_{s}
{
std::cout << "ctor: " << s_ << std::endl;
}
~Object()
{
std::cout << "dtor: " << s_ << std::endl;
}
// Explicitly no copy constructor!
Object(const Object& other) = delete;
Object(Object&& other)
{
std::swap(s_, other.s_);
std::cout << "move: traded '" << s_ << "' for '" << other.s_ << "'" << std::endl;
}
Object& operator=(Object other)
{
std::swap(s_, other.s_);
std::cout << "assign: " << s_ << std::endl;
return *this;
}
private:
std::string s_;
};
using Objects = std::vector<Object>;
Object createObject(const std::string& s)
{
Object o{s};
return o;
}
int main ()
{
Objects v;
v.reserve(4); // avoid moves, if initial capacity is too small
std::cout << "capacity(v): " << v.capacity() << std::endl;
Params ps = { "a", "bb", "ccc", "dddd" };
for (auto p : ps) {
v.push_back(createObject(p));
}
return 0;
}
请注意,class Object
明确禁止复制。但要实现这一点,移动构造必须可用。
有关何时可以(或将要)发生复制省略的详细摘要here。
答案 1 :(得分:1)
移动语义和向量的复制应该意味着本地std::vector
的元素实际上是从对象传递到局部变量中。
粗略地说,你可以期待std::vector
的移动构造函数类似于:
//This is not real code...
vector::vector(vector&& tomove){
elems=tomove.elems; //cheap transfer of elements - no copying of objects.
len=tomove.elems;
cap=tomove.cap;
tomove.elems=nullptr;
tomove.len=0;
tomove.cap=0;
}
执行此代码并注意构造和销毁最小数量的对象。
#include <iostream>
#include <vector>
class Heavy{
public:
Heavy(){std::cout<< "Heavy construction\n";}
Heavy(const Heavy&){std::cout<< "Heavy copy construction\n";}
Heavy(Heavy&&){std::cout<< "Heavy move construction\n";}
~Heavy(){std::cout<< "Heavy destruction\n";}
};
std::vector<Heavy> build(size_t size){
std::vector<Heavy> result;
result.reserve(size);
for(size_t i=0;i<size;++i){
result.emplace_back();
}
return result;
}
int main() {
std::vector<Heavy> local=build(5);
std::cout<<local.size()<<std::endl;
return 0;
}
移动语义和复制elison倾向于处理这个问题C ++ 11以后。
预期产出:
Heavy construction
Heavy construction
Heavy construction
Heavy construction
Heavy construction
5
Heavy destruction
Heavy destruction
Heavy destruction
Heavy destruction
Heavy destruction
请注意,我在填充之前保留了向量中的容量,并使用emplace_back
将对象直接构造到向量中。
你不必将传递给reserve
的值完全正确,因为向量将增长以适应值,但最终将导致重新分配和移动所有元素根据您或编译器是否实现了有效的移动构造函数,代价很高。