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

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

有没有什么方法可以将函数在栈内存上创建的对象的所有权传递给函数外部而不使用拷贝构造?

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

这是一种常见的情况:

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

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

嗯,智能指针也许是一个解决方案。但是……不那么优雅,我想。呵呵

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

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

通过此优化,returned 对象的 space 是从调用上下文(外部堆栈框架)分配的,并直接在那里构建对象。

在您的示例中,编译器将在调用 createObject() 的上下文中为对象分配 space。由于这个上下文是 std::vector<T>.push_back() 成员函数的一个(未命名的)参数,它作为一个右值引用,所以 push_back() 按值将通过将它移动(而不是复制)到向量。这是可能的,因为如果生成的对象是可移动的。否则会出现复制。

总而言之,将创建每个对象,然后将其移动(如果可移动)到向量中。

下面是一个示例代码,更详细地展示了这一点:

#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

向量的移动语义和复制 elison 应该意味着局部 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;
}

从 C++11 开始,移动语义和复制 elison 倾向于解决这个问题。

预期输出:

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

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

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