【问题标题】:How to optimize object return in C++?如何在 C++ 中优化对象返回?
【发布时间】:2012-07-24 15:09:26
【问题描述】:

考虑下面的代码。

// Consider that MyObject receives three integers in it's constructor.
MyObject createObject()
{
    MyObject result(1, 2, 3);
    return result;
}

据我所知,当您在 C++ 中返回一个对象时(就像在这个函数中一样),编译器会在堆栈中为客户端创建一个新对象(作为局部变量)。在这个例子中,它将是一个新的MyObject 对象,它将使用复制构造函数以result 作为参数创建。这意味着机器的冗余处理:对象result 将在堆栈中创建,然后将创建并返回第二个对象。 避免这种情况的另一种方法是动态创建对象(不在堆栈中),然后返回它的指针。但这会导致内存泄漏或强制客户端在使用后删除对象。因此,我找到的解决方案是使用智能指针,如下所示:

// Consider that MyObject receives three integers in it's constructor.
boost::smart_ptr<MyObject> createObject()
{
    boost::smart_ptr<MyObject> result(new MyObject(1, 2, 3));
    return result;
}

这可行,但智能指针仍然是一个将被重新创建的对象(即使成本很低,因为基本上它只包含一个指针)。我在猜测是否有一种方法可以更轻松地做到这一点;或者如果编译器还没有实现优化这项工作的方法或类似的东西。有没有语法告诉编译器做我想做的事?

【问题讨论】:

  • 是的,请务必阅读 RVO。不,如果您追求性能,请不要使用智能指针(分配可能比任何可能的副本花费更多)。而且您通常可以只要求调用者分配并传入一个非常量引用(丑陋但有效)。
  • @Keith 但随后分配给参数相当于复制它...
  • 除非对象有外部资源,否则你无法真正优化它。如果是这样,C++11 将注意一个简单的返回将移动本地对象。如果对象是扁平的(也就是没有指向外部资源的指针/句柄),我会简单地获取副本,如果它来了,它应该不会花费太多(你不想拥有大的扁平对象,担心低效返回仅与具有外部资源的类型一起提供)。
  • @Luchian:如果它有外部资源,它可以有一种方法从另一个对象“窃取”,即使在没有移动语义的 C++03 中,通过显式请求它(如 result_ref.take_from(other)) .或者你甚至可以使用 result_ref 来做你想做的一切。
  • 过早优化。使用最简单最易维护的方法(第一个版本)。只有当它很慢时才尝试优化并且您可以测量它,以便您可以将它与尝试的优化进行比较。如果你这样做,我敢打赌共享指针在很多情况下可能会更慢。

标签: c++ optimization object compiler-construction return


【解决方案1】:

如果你愿意使用 C++11,你可以使用“移动语义”。我先举个例子解释一下:

class expensive {
public:
     expensive() : n(0), V(NULL) {}
     expensive(int _n) {
         n = _n;
         V = new int[_n];
         for (int i = 0; i < n; ++i) V[i] = i*i;
     }

     expensive(const expensive& rhs) { // copy constructor
          n = rhs.n;
          V = new int[n];
          for (int i = 0; i < n; ++i) V[i] = rhs.V[i];
     }
     expensive(expensive&& rhs) { //move constructor
          n = rhs.n;
          V = rhs.V;
          rhs.n = -1;
          rhs.V = NULL;
          printf("Moving\n");
     }
     ~expensive() {
          if (n == -1) printf("Destroying 'moved' instance\n");
          if (V) delete [] V;
     }
private:
     int *V;
     int n;
};
expensive f(int x) {
    expensive temp(50);
    expensive temp2(temp); // Copy temp to temp2, both are valid now
    expensive temp3(std::move(temp)); // Move temp to temp3, temp is not valid anymore
    return std::move(temp2); // move temp2 to the return 
}
int main() {
    expensive E = f(30);
    return 0;
}

这个程序的输出是:

Moving
Moving
Destroying 'moved' instance
Destroying 'moved' instance

所有普通的 STL 容器都支持移动语义。此外,std::swap 使用它(因此,std::sort 也是如此)。

编辑: 如前所述,如果正在使用 NRVO,则最后一个 std::move 是不必要的。让我举一个更复杂的例子。这是一个玩具示例,但它应该说明我的观点。

class expensive {
    ... // Same definition as before
}
expensive g(int x) {
    vector<expensive> V;
    V.reserve(2*x);
    for (int i = 1; i <= x; ++i)
         V.push_back(expensive(i)); // Here the objects are moved to the vector
    for (int i = 1; i <= x; ++i)
         V.emplace_back(i); // Here the objects are constructed directly in the vector
    return std::move(V[x/2]); // V[x/2] is moved to the answer
}
int main() {
    expensive x(g(2)); // 3 objects should be moved, 2 in push_back and one on the return
    return 0;
}

【讨论】:

  • 如果正在执行 NRVO,则无需返回移动。因为它是在目的地 E 上构建的,因此不需要复制回来。
  • 析构函数中的if (V) 不是必需的。 delete[] NULL 完全没问题。此外,您的班级缺少复制赋值运算符和移动赋值运算符。此外,从自动对象移动是隐式的,请参阅“移出函数”下的 here
【解决方案2】:

来源:http://blogs.msdn.com/b/slippman/archive/2004/02/03/66739.aspx

编译器优化器足够聪明,可以弄清楚发生了什么,所以在优化时它会在内部重写调用:

void CallingFunction()
{
    // Call to createObject() gets eliminated through transformation
    // since in this case it's only code was the initialization
    MyObject obj(1, 2, 3);

    // ... do stuff
}

【讨论】:

    【解决方案3】:

    首先分析您的代码,我 99% 确定复制不会产生任何开销。

    NRVO 存在并且很可能会在这种情况下发挥作用。

    如果不是复制省略,返回集合(如std::vector 等)会导致大量问题。

    当您在 C++ 中返回一个对象时(如在此函数中),编译器将在堆栈中为客户端创建一个新对象(作为局部变量)。

    不是真的。它可能会直接在调用上下文中创建,正是为了防止额外的副本。

    【讨论】:

    • 这是一个纯粹的假设问题,我还没有遇到过这个问题。这是我个人的疑问。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-17
    • 2013-09-18
    • 1970-01-01
    • 2011-03-21
    • 2010-09-17
    相关资源
    最近更新 更多