如何将对象的所有权传递给函数的外部

How to pass the ownership of an object to the outside of the function

本文关键字:函数 外部 所有权 对象      更新时间:2023-10-16

是否有任何方法可以在不使用复制构造的情况下将堆栈内存上函数中创建的对象的所有权传递给函数外部?

通常,编译器会自动对函数堆栈上的对象调用销毁。因此,如果我们想创建一个类的对象(可能带有一些特定的参数(,我们如何避免浪费大量资源从临时对象复制?

下面是一个常见情况:

while(...){
vectors.push_back(createObject( parameter ));
}

所以当我们想在迭代中创建一些参数的对象,并将它们推送到一个vector时,正常值传递的方式会花费大量的时间复制对象。我不想在堆内存上使用指针和new对象,因为用户可能会忘记delete它们,从而导致内存泄漏

好吧,智能指针也许是一个解决方案。但。。我认为不那么优雅。呵

有没有办法应用rvalue referencemove语义来解决这个问题?

通常,按值返回对象不会复制该对象,因为编译器应该执行(命名的(返回值优化,从而省略副本。

通过此优化,返回对象的空间从调用上下文(外部堆栈帧(分配,并且对象直接在那里构造。

在您的示例中,编译器将在调用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明确禁止复制。但要使其正常工作,移动构造函数必须可用。

有关何时可以(或将(发生复制的详细摘要,请点击此处。

移动语义和复制向量应该意味着局部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 constructionn";}
Heavy(const Heavy&){std::cout<< "Heavy copy constructionn";}
Heavy(Heavy&&){std::cout<< "Heavy move constructionn";}
~Heavy(){std::cout<< "Heavy destructionn";}
};

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 倾向于在 11 C++

开始解决这个问题。预期产出:

Heavy construction
Heavy construction
Heavy construction
Heavy construction
Heavy construction
5
Heavy destruction
Heavy destruction
Heavy destruction
Heavy destruction
Heavy destruction

请注意,我在填充向量之前在向量中保留了容量,并使用emplace_back将对象直接构造到向量中。

您不必将值传递给reserve完全正确,因为向量将增长以适应值,但它最终将导致所有元素的重新分配和移动,这可能会很昂贵,具体取决于您或编译器是否实现了有效的移动构造函数。