使用“避免复制对象”;return";陈述 我在C++中有一个非常基本的问题。 返回对象时如何避免复制

使用“避免复制对象”;return";陈述 我在C++中有一个非常基本的问题。 返回对象时如何避免复制,c++,object,copy,return,return-value,C++,Object,Copy,Return,Return Value,以下是一个例子: std::vector<unsigned int> test(const unsigned int n) { std::vector<unsigned int> x; for (unsigned int i = 0; i < n; ++i) { x.push_back(i); } return x; } std::向量测试(常量无符号整数n) { std::向量x; for(无符号整数i=0;i当我

以下是一个例子:

std::vector<unsigned int> test(const unsigned int n)
{
    std::vector<unsigned int> x;
    for (unsigned int i = 0; i < n; ++i) {
        x.push_back(i);
    }
    return x;
}
std::向量测试(常量无符号整数n)
{
std::向量x;
for(无符号整数i=0;i
<> P>当我理解C++如何工作时,这个函数将创建2个向量:局部向量(x)和将被返回的x的拷贝。有没有办法避免复制?(我不想返回指向对象的指针,但要返回对象本身)



使用“移动语义”(注释中有说明)该函数的语法是什么?

该程序可以利用命名返回值优化(NRVO)。请看这里:


在C++11中,移动构造函数和赋值也很便宜。您可以在此处阅读教程:

编译器通常可以为您优化多余的副本(这称为返回值优化)。请参见将为您完成此工作,因为编译器在使用它时会尝试消除冗余的复制构造函数和析构函数调用

std::vector<unsigned int> test(const unsigned int n){
    std::vector<unsigned int> x;
    return x;
}
...
std::vector<unsigned int> y;
y = test(10);
检查他的答案,以便更好地理解这种情况(您还会发现,这种优化并不总是适用的)

您可以修改代码,将向量的引用传递给函数,这样在避免复制的同时语义上更正确:

void test(std::vector<unsigned int>& x){
    // use x.size() instead of n
    // do something with x...
}
...
std::vector<unsigned int> y;
test(y);
无效测试(std::vector&x){
//使用x.size()而不是n
//用x做点什么。。。
}
...
std::向量y;
试验(y);

首先,您可以将返回类型声明为std::vector&在这种情况下,将返回引用而不是副本

您还可以定义一个指针,在方法体中构建一个指针,然后返回该指针(或该指针的副本以确保正确)


最后,许多C++编译器可以返回值优化。http://en.wikipedia.org/wiki/Return_value_optimization)在某些情况下删除临时对象。

引用它会起作用

Void(vector<> &x) {

}
Void(向量和x){
}

关于RVO(返回值优化)的工作原理,似乎存在一些混淆

一个简单的例子:

#include <iostream>

struct A {
    int a;
    int b;
    int c;
    int d;
};

A create(int i) {
    A a = {i, i+1, i+2, i+3 };
    std::cout << &a << "\n";
    return a;
}

int main(int argc, char*[]) {
    A a = create(argc);
    std::cout << &a << "\n";
}
令人惊讶?

实际上,这就是RVO的效果:要返回的对象直接在调用者中就地构造

怎么做?

传统上,调用方(
main
此处)将在堆栈上为返回值保留一些空间:返回槽;被调用方(
create
此处)以某种方式传递返回槽的地址,以将其返回值复制到其中。然后,被调用者为构建结果的局部变量分配自己的空间,就像其他任何局部变量一样,然后在
return
语句中将其复制到返回槽中

当编译器从代码中推断变量可以直接构造到具有等效语义的返回槽中时,就会触发RVO(假设规则)

请注意,这是一种常见的优化,标准中明确列出了它,编译器不必担心复制(或移动)构造函数可能产生的副作用

什么时候?

编译器最有可能使用简单的规则,例如:

// 1. works
A unnamed() { return {1, 2, 3, 4}; }

// 2. works
A unique_named() {
    A a = {1, 2, 3, 4};
    return a;
}

// 3. works
A mixed_unnamed_named(bool b) {
    if (b) { return {1, 2, 3, 4}; }

    A a = {1, 2, 3, 4};
    return a;
}

// 4. does not work
A mixed_named_unnamed(bool b) {
    A a = {1, 2, 3, 4};

    if (b) { return {4, 3, 2, 1}; }

    return a;
}
在后一种情况(4)中,当返回
A
时,无法应用优化,因为编译器无法在返回槽中生成
A
,因为它可能需要它来执行其他操作(取决于布尔条件
b

因此,一个简单的经验法则是:


如果在
return
语句之前没有声明返回槽的其他候选项,则应应用RVO。如果没有发生NRVO,则保证使用移动构造函数

因此,如果您按值返回具有移动构造函数(例如
std::vector
)的对象,则即使编译器未能执行可选的NRVO优化,也保证不会执行完整的向量复制

这两个用户在C++规范中表现出了影响:

  • 约翰清醒地看着我
  • 霍华德·希南特
不满足于我对名人的吸引力

嗯。我不能完全理解C++标准,但我能理解它的例子!!-)p> 引用15.8.3[class.copy.elision]“复制/移动省略”

3在以下复制初始化上下文中,可以使用移动操作代替复制操作:

  • (3.1)-如果返回语句(9.6.3)中的表达式是一个(可能带括号)id表达式,则 对象的主体或参数声明子句中声明了自动存储持续时间的对象 最内层的封闭函数或lambda表达式,或
  • (3.2)-如果抛出表达式(8.17)的操作数是非易失性自动对象的名称(除 函数或catch子句参数),其作用域不超出最内层 封闭try块(如果有)
首先执行重载解析以选择副本的构造函数,就像指定了对象一样 以右值。如果第一个重载解析失败或未执行,或者如果第一个参数的类型 所选构造函数的不是对对象类型的右值引用(可能是cv限定的),重载 再次执行解析,将对象视为左值。[注:此两级过载解决方案 无论是否会发生复制省略,都必须执行。如果 不执行省略,即使调用被省略,所选构造函数也必须可访问。-结束 注]

4[举例:

class Thing {
public:
  Thing();
  ~ Thing();
  Thing(Thing&&);
private:
  Thing(const Thing&);
};

Thing f(bool b) {
  Thing t;
  if (b)
    throw t;          // OK: Thing(Thing&&) used (or elided) to throw t
  return t;           // OK: Thing(Thing&&) used (or elided) to return t
}

Thing t2 = f(false);  // OK: no extra copy/move performed, t2 constructed by call to f

struct Weird {
  Weird();
  Weird(Weird&);
};

Weird g() {
  Weird w;
  return w;           // OK: first overload resolution fails, second overload resolution selects Weird(Weird&)
}
-结束示例

我不喜欢“可能被使用”的措辞,但我认为
// 1. works
A unnamed() { return {1, 2, 3, 4}; }

// 2. works
A unique_named() {
    A a = {1, 2, 3, 4};
    return a;
}

// 3. works
A mixed_unnamed_named(bool b) {
    if (b) { return {1, 2, 3, 4}; }

    A a = {1, 2, 3, 4};
    return a;
}

// 4. does not work
A mixed_named_unnamed(bool b) {
    A a = {1, 2, 3, 4};

    if (b) { return {4, 3, 2, 1}; }

    return a;
}
class Thing {
public:
  Thing();
  ~ Thing();
  Thing(Thing&&);
private:
  Thing(const Thing&);
};

Thing f(bool b) {
  Thing t;
  if (b)
    throw t;          // OK: Thing(Thing&&) used (or elided) to throw t
  return t;           // OK: Thing(Thing&&) used (or elided) to return t
}

Thing t2 = f(false);  // OK: no extra copy/move performed, t2 constructed by call to f

struct Weird {
  Weird();
  Weird(Weird&);
};

Weird g() {
  Weird w;
  return w;           // OK: first overload resolution fails, second overload resolution selects Weird(Weird&)
}
void test(const unsigned int n, std::vector<int>& x) {
    x.resize(0);
    x.reserve(n);
    for (unsigned int i = 0; i < n; ++i) {
        x.push_back(i);
    }
}

std::vector<int> x;
test(10, x);
test(20, x);
test(10, x);