C++对象作为返回值:复制还是引用?

use*_*208 10 c++ memory return object

我想测试当函数的返回值是一个对象时C++的行为.我做了一个小例子来观察分配了多少字节,并确定编译器是否复制了对象(比如当对象作为参数传递时)或者返回某种引用.

但是,我无法运行这个非常简单的程序而且我不知道为什么.错误说:"某些dbgdel.cpp文件中的"调试断言失败!表达式:BLOCK_TYPE_IS_INVALID".Project是一个win32控制台应用程序.但我很确定这段代码有问题.

class Ctest1
{
public:
   Ctest1(void);
   ~Ctest1(void);

   char* classSpace;
};

Ctest1::Ctest1(void)
{
   classSpace = new char[100];
}

Ctest1::~Ctest1(void)
{
   delete [] classSpace;
}

Ctest1 Function(Ctest1* cPtr){
   return *cPtr;    
}

int _tmain(int argc, _TCHAR* argv[])
{
   Ctest1* cPtr;

   cPtr=new Ctest1();


   for(int i=1;i<10;i++)
      *cPtr = Function(cPtr);


   delete cPtr;

   return 0;
   }
Run Code Online (Sandbox Code Playgroud)

Rob*_*obᵩ 14

你违反了三条规则.

具体而言,当你返回一个对象,一个副本制成,然后销毁.所以,你有一系列的事件,如

Ctest1::Ctest1(void);
Ctest1::Ctest1(const Ctest1&);
Ctest1::~Ctest1();
Ctest1::~Ctest1();
Run Code Online (Sandbox Code Playgroud)

这是创建了两个对象:原始对象构造,后跟隐式复制构造函数.然后删除这两个对象.

由于这两个对象都包含相同的指针,因此最终会delete在同一个值上调用两次. 繁荣


额外信用:当我调查诸如"我想知道副本是如何制作"之类的问题时,我将print语句放在有趣的类方法中,如下所示:

#include <iostream>

int serial_source = 0;
class Ctest1
{
#define X(s) (std::cout << s << ": " << serial << "\n")
  const int serial;
public:
   Ctest1(void) : serial(serial_source++) {
     X("Ctest1::Ctest1(void)");
   }
   ~Ctest1(void) {
    X("Ctest1::~Ctest1()");
   }
   Ctest1(const Ctest1& other) : serial(serial_source++) {
    X("Ctest1::Ctest1(const Ctest1&)");
    std::cout << " Copied from " << other.serial << "\n";
   }
   void operator=(const Ctest1& other) {
     X("operator=");
     std::cout << " Assigning from " << other.serial << "\n";
   }
#undef X
};

Ctest1 Function(Ctest1* cPtr){
   return *cPtr;    
}

int main()
{
   Ctest1* cPtr;

   cPtr=new Ctest1();


   for(int i=1;i<10;i++)
      *cPtr = Function(cPtr);

   delete cPtr;

   return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 虽然你是对的,但这个答案将毫无意义,除非OP已经知道这意味着什么.你能说得更具描述性吗? (3认同)
  • 如果您没有看到额外的 copy-ctor 调用,`-fno-elide-constructors` 标志可能会很有用。 (2认同)

Jer*_*fin 8

得到(最终)您最初打算询问的问题,简短的回答是这很少成为问题。该标准包含一个条款,该条款明确免除编译器必须在返回值上实际使用复制构造函数,即使复制构造函数有副作用,因此差异是外部可见的。

根据您返回的是变量还是仅返回值,这称为命名返回值优化 (NRVO) 或仅返回值优化 (RVO)。大多数合理的现代编译器都实现了两者(有些,例如 g++ 甚至在您关闭优化时也会这样做)。

为了避免复制返回值,编译器所做的是将复制将作为隐藏参数的地址传递给函数。函数然后在那个地方构造它的返回值,所以在函数返回后,该值已经存在而没有被复制。

这很常见,而且效果很好,以至于 Dave Abrahams(当时是 C++ 标准委员会成员)在几年前写了一篇文章表明,使用现代编译器,人们避免额外复制的尝试通常实际上产生的代码比只需编写简单、明显的代码即可。


小智 5

正如 Rob 所说,您还没有创建 C++ 使用的所有三个构造函数/赋值运算符。他提到的三法则的意思是,如果你声明了一个析构函数、复制构造函数或赋值运算符(operator=()),你需要同时使用这三个。

如果您不创建这些函数,那么编译器将为您创建它们自己的版本。但是,编译器复制构造函数和赋值运算符只对原始对象中的元素进行浅拷贝。这意味着作为返回值创建然后复制到对象中的复制对象main()具有指向与您创建的第一个对象相同地址的指针。因此,当原始对象被销毁以为复制的对象腾出空间时,堆上的 classSpace 数组将被释放,从而导致复制对象的指针失效。