C++ 从函数创建和返回大对象
想象这样的情况,我有这样一个函数:C++ 从函数创建和返回大对象,c++,c,memory,C++,C,Memory,想象这样的情况,我有这样一个函数: Object f() { Object obj; return obj; } 其中sizeof(Object)是一个大值 然后我调用这个函数: Object object = f(); 我是否正确理解第一个对象将在堆栈(函数中)上创建,然后复制到对象变量 如果是这样,在堆上的函数中创建对象并返回指向该对象的指针而不是副本是否合理 但我的意思是必须在f()函数中创建对象,而不是通过指针或对该函数的引用来传递并初始化 编辑 我不是说f是一个
Object f()
{
Object obj;
return obj;
}
其中sizeof(Object)
是一个大值
然后我调用这个函数:
Object object = f();
我是否正确理解第一个对象将在堆栈(函数中)上创建,然后复制到对象变量
如果是这样,在堆上的函数中创建对象并返回指向该对象的指针而不是副本是否合理
但我的意思是必须在f()
函数中创建对象,而不是通过指针或对该函数的引用来传递并初始化
编辑
我不是说f是一个非常简单的函数。它可能有一个非常复杂的对象初始化例程,具体取决于某些上下文。编译器还会对其进行优化吗?对于这种特定情况,您可以利用现在的编译器足够智能,可以针对它进行优化。优化被称为命名(NRVO),因此可以返回这样的“大”对象。编译器可以看到这样的机会(特别是在像代码片段这样简单的事情中),并生成二进制文件,这样就不会产生副本 还可以返回未命名的临时对象:
Object f()
{
return Object();
}
这调用了几乎所有现代C++编译器的<强>(未命名)返回值优化(RVO)<强>。事实上,Visual C++实现了这个特定的优化,即使所有优化都关闭了。
<>这些优化是C++标准所允许的:
<强> ISO14822003,C++标准,第12.8版。15: 复制类对象
当满足某些标准时,一个 允许实现省略 复制类对象的构造, 即使复制构造函数和/或 对象的析构函数有边 影响。在这种情况下 实现处理源代码和源代码 省略的复制操作的目标 仅仅是两种不同的方式 指同一对象,并且 该对象发生破坏 后来两个 物体会被摧毁 没有优化。这是埃里森 允许在 以下情况(可能是: 合并以消除多个 副本):- 在具有类terturn类型的函数中的
语句中, 当表达式是 非易失性自动对象,具有 与客户相同的cv不合格类型 函数返回类型,复制 操作可以通过以下方式省略: 构造自动对象 直接输入函数的返回 价值观return
- 当尚未绑定到引用的临时类对象 将复制到具有 相同的cv不合格类型,副本 操作可以通过以下方式省略: 构建临时对象 直接进入目标的 省略的副本
class Foo
{
public:
Foo() { ::printf("default constructor\n"); }
// "Rule of 3" for copyable objects
~Foo() { ::printf("destructor\n"); }
Foo(const Foo&) { ::printf("copy constructor\n"); }
Foo& operator=(const Foo&) { ::printf("copy assignment\n"); }
};
Foo getFoo()
{
return Foo();
}
int main()
{
Foo f = getFoo();
}
如果返回的对象不可复制,或者(N)RVO失败(这可能不太可能发生),则可以尝试返回代理对象:
struct ObjectProxy
{
private:
ObjectProxy() {}
friend class Object; // Allow Object class to grab the resource.
friend ObjectProxy f(); // Only f() can create instances of this class.
};
class Object
{
public:
Object() { ::printf("default constructor\n"); }
~Object() { ::printf("destructor\n"); }
// copy functions undefined to prevent copies
Object(const Object&);
Object& operator=(const Object&);
// but we can accept a proxy
Object(const ObjectProxy&)
{
::printf("proxy constructor\n");
// Grab resource from the ObjectProxy.
}
};
ObjectProxy f()
{
// Acquire large/complex resource like files
// and store a reference to it in ObjectProxy.
return ObjectProxy();
}
int main()
{
Object o = f();
}
当然,这并不是很明显,所以需要适当的文档(至少是一个评论)
您还可以将某种类型的智能指针(如std::auto_ptr
或boost::shared_ptr
或类似的)返回到免费存储上分配的对象。如果需要返回派生类型的实例,则需要执行此操作:
class Base {};
class Derived : public Base {};
// or boost::shared_ptr or any other smart pointer
std::auto_ptr<Base> f()
{
return std::auto_ptr<Base>(new Derived);
}
类基{};
派生类:公共基{};
//或boost::shared_ptr或任何其他智能指针
std::auto_ptr f()
{
返回std::auto_ptr(新派生);
}
理论上,你所描述的就是应该发生的事情。无论如何,编译器通常能够以某种方式对其进行优化,即使用调用方的对象
:f
将直接写入调用方的对象并返回null
这称为(或RVO)
首先,我是否正确理解了这一点
对象将在堆栈上创建(在
然后将被复制
对象变量
是的,obj是在堆栈上创建的,但是当您返回一个称为返回值优化或RVO的过程时,可以防止不必要的复制
如果是,是否合理地创建
堆上函数中的对象,然后
返回指向它的指针而不是
收到了吗
是的,在堆上创建一个对象并返回指向它的指针是合理的,只要您清楚地记录了客户机负责清理内存
但是,返回智能指针(如
shared_ptr
)比返回智能指针更合理,这样客户端就不必记住显式释放内存。编译器将对其进行优化
除非在某些情况下:
当然,也可能有一些旧的编译器,它们可以调用复制构造函数。但在现代编译器中,您不必担心它。如果函数f是工厂方法,最好返回指针或初始化的智能指针对象,如auto_ptr
auto_ptr<Object> f()
{
return auto_ptr<Object>(new Object);
}
auto_ptr f()
{
返回自动_ptr(新对象);
}
使用:
{
auto_ptr<Object> myObjPtr = f();
//use myObjPtr . . .
} // the new Object is deleted when myObjPtr goes out of scope
{
自动_ptr myObjPtr=f();
//使用myObjPtr。
}//当myObjPtr超出范围时,新对象将被删除
编译器是否可以应用RVO取决于所涉及的实际代码。一般的指导原则是尽可能晚地创建返回值。例如:
std::string no_rvo(bool b) {
std::string t = "true", f = "fals";
f += t[3]; // Imagine a "sufficiently smart compiler" couldn't delay initialization
// for some reason, such not noticing only one object is required depending on some
// condition.
//return (b ? t : f); // or more verbosely:
if (b) {
return t;
}
return f;
}
std::string probably_rvo(bool b) {
// Delay creation until the last possible moment; RVO still applies even though
// this is superficially similar to no_rvo.
if (b) {
return "true";
}
return "false";
}
对于C++0x,编译器可以自由地做出更多假设,主要是通过能够使用移动语义。这些是如何工作的是另一个可以蠕虫,但移动语义
std::string no_rvo(bool b) {
std::string t = "true", f = "fals";
f += t[3]; // Imagine a "sufficiently smart compiler" couldn't delay initialization
// for some reason, such not noticing only one object is required depending on some
// condition.
//return (b ? t : f); // or more verbosely:
if (b) {
return t;
}
return f;
}
std::string probably_rvo(bool b) {
// Delay creation until the last possible moment; RVO still applies even though
// this is superficially similar to no_rvo.
if (b) {
return "true";
}
return "false";
}
void f(Object& result) {
result.do_something();
result.fill_with_values(/* */);
};