C++对象作为返回值:复制或引用
C++ object as return value: copy or reference?
我想测试当函数的返回值是对象时C++的行为。我举了这个小例子来观察分配了多少字节,并确定编译器是复制对象(比如将对象作为参数传递时)还是返回某种引用。
然而,我不能运行这个非常简单的程序,我不知道为什么。错误显示:"调试断言失败!表达式:BLOCK_TYPE_IS_INVALID"在某个dbgdel.cpp文件中。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;
}
你违反了三条规则。
具体来说,当您返回一个对象时,会生成一个副本,然后销毁。所以,你有一系列类似的事件
Ctest1::Ctest1(void);
Ctest1::Ctest1(const Ctest1&);
Ctest1::~Ctest1();
Ctest1::~Ctest1();
也就是说,创建了两个对象:原始对象构造,然后是隐式复制构造函数。然后这两个对象都被删除。
由于这两个对象都包含相同的指针,因此最终会对同一值调用delete
两次动臂
额外学分:当我调查诸如"我想知道副本是如何制作的"之类的问题时,我将打印语句放在有趣的类方法中,比如:
#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;
}
(最终)了解您最初想要询问的内容,简短的回答是这很少是问题。该标准包含一个子句,该子句专门免除编译器在返回值上实际使用复制构造函数的必要性,即使复制构造函数有副作用,因此差异在外部是可见的。
根据返回变量还是仅返回值,这被称为命名返回值优化(NRVO)或仅返回值优化。大多数相当现代的编译器都实现这两种功能(有些编译器,如g++,甚至在关闭优化时也能实现)。
为了避免复制返回值,编译器所做的是将副本所在的地址作为隐藏参数传递给函数。然后,函数在该位置构造其返回值,因此在函数返回后,该值已经存在,而不会被复制。
这很常见,而且效果很好,以至于Dave Abrahams(当时是C++标准委员会成员)几年前写了一篇文章,表明在现代编译器中,人们试图避免额外的复制,实际上产生的代码比只写简单、明显的代码要慢。
operator=()
),你需要使用这三个。
如果您不创建这些函数,那么编译器将为您创建自己版本的函数。但是,编译器复制构造函数和赋值运算符只对原始对象中的元素进行浅层复制。这意味着,创建为返回值,然后复制到main()
中的对象中的复制对象有一个指向与您创建的第一个对象相同地址的指针。因此,当原始对象被销毁以为复制的对象腾出空间时,堆上的classSpace数组就会被释放,从而导致复制对象的指针失效。
如果您想查看何时创建对象的副本,请执行以下操作:
struct Foo {
Foo() { std::cout << "default ctorn"; }
Foo(Foo const &) { std::cout << "copy ctorn"; }
Foo(Foo &&) { std::cout << "move ctorn"; }
Foo &operator=(Foo const &) { std::cout << "copy assignn"; return *this; }
Foo &operator=(Foo &&) { std::cout << "move assignn"; return *this; }
~Foo() { std::cout << "dtorn"; }
};
Foo Function(Foo* f){
return *f;
}
int main(int argc,const char *argv[])
{
Foo* f=new Foo;
for(int i=1;i<10;i++)
*f = Function(f);
delete f;
}
- 深层复制具有自引用指针的类
- 对复制 CTOR 和 CTOR 的未定义引用
- 为什么我的运算符 + 重载尽管是通过引用传递的,但仍调用我的复制构造函数?
- 在引用初始化中使用已删除的复制构造函数进行复制初始化
- 关于隐式声明的复制构造函数的引用在逻辑上不清楚
- C++:右值引用构造函数和复制省略
- 通过基类接受方法转发派生 UniquePtr 的右值会移动引用而不是复制
- 如何复制只引用基类的派生类
- 为什么定义复制构造函数会给我错误:无法将类型 'obj&' 的非常量左值引用绑定到类型为"obj"的右值?
- C++为具有引用成员变量的类创建复制构造函数
- std::vector 是否有用于引用的复制构造函数?
- 是复制了新数组还是指向结构中引用的数组的指针?
- 可移动但不可复制的对象:按值传递还是按引用传递?
- 通过引用传递是否涉及复制地址
- 复制引用结构上的赋值
- 在 c++ 中复制对未初始化对象的引用
- C++自动生成的具有常量和非常量引用的复制构造函数
- 如何保存指向抽象基类的指针/引用,但在 c++ 中仍然可以复制
- Icpc错误隐式生成的赋值运算符无法复制引用成员(boost图)
- 函数复制引用字符数组中的数据