C++ 函数可以直接返回一个对象吗?

您所在的位置:网站首页 赵雷有对象吗 C++ 函数可以直接返回一个对象吗?

C++ 函数可以直接返回一个对象吗?

2024-07-16 14:00| 来源: 网络整理| 查看: 265

内存和资源管理是 C++ 最强的能力之一,也是 C++ 最复杂和最需要思考的地方。写 Java 的时候,我们只需要无脑地把所有对象都 new 出来。反正所有的对象只能放在堆区,又反正又垃圾回收器帮我们管理内存。然而,在 C++ 中,我们需要思考是把对象放在栈上,还是用 new 把对象放在堆上。默认情况下,对象会放在栈上,这样的好处是我们不会忘记释放对象的内存而造成内存泄漏。不过如果我们把一个大对象放在栈上,又将其作为参数或者返回值传递,就必须要考虑对象拷贝的开销了。C++ 由于和 C 兼容,默认情况下参数是按值传递 (call by value) 的,在传递参数和返回值的时候都会拷贝一遍对象。对于参数,我们尚可以将参数声明为引用类型 T& 来避免对象拷贝。而对于返回值的拷贝开销,则是不能声明为引用类型来解决的。

何时必须返回一个对象

假设我们想写一个 range 函数:

1234567vector range(int begin, int end, int step=1) { vector res; for (int i = begin; i < end; i += step) { res.push_back(i); } return res;}

这段代码会返回一个 vector 对象,也就是我们不希望看到的:放在栈上的大对象。调用这个函数会产生返回值的临时对象,从而需要拷贝列表中的所有元素。很显然,你不能直接把返回值类型改成 vector& 来避免对象拷贝——编译器会产生一个警告:warning: reference to local variable ‘res’ returned,你返回了一个临时变量的引用,这个引用指向了一个栈上的地址,而这个地址随时可能被回收。这也是 C++ 初学者容易犯的一个错误。既然不能返回一个引用,又想避免对象拷贝的开销,很多“老” C++ 程序员会进行一个人肉优化:把返回值作为引用参数传进去。按这种方法,range() 函数可以改写如下:

123456789vector range(vector& out, int begin, int end, int step=1) { for (int i = begin; i < end; i += step) { out.push_back(i); }}// Callervector r;range(r, 0, 10);

C/C++ 程序员可能非常习惯这样写。然而,必须承认这是一个丑陋的写法。那么,直接返回一个 vector 对象到底会怎么样呢?对象拷贝的开销能否避免?

临时对象与返回值优化

根据 《深度探索 C++ 对象模型》第 2.3 节 “程序转化语意学” (Program Transformation Semantics) 所述,函数的返回值会做如下转化:

添加一个临时变量 __result 当函数返回时,调用 __result 的 copy constructor,使用返回值 x 作为参数 (如果有的话)对 __result 进行后续操作

注意,后续操作中还能包括更多的 constructor,例如我们调用 range 函数以初始化变量 r1:

12vector r1;r1 = range(1, 10); // 调用 r1 的 copy assignment operator (即 operator=)

可以看到,虽然只是简单的一次函数调用,临时对象就进行了两次拷贝。而 range 产生的数越多,需要拷贝的内容就越多,对性能的影响就越大。

为了解决这个问题,很多 C++ 编译器都实现了 返回值优化 (Return Value Optimization),来消除返回值临时对象的多次拷贝。

一个例子

为了验证编译器进行返回值优化前后的不同,我们运行一个完整的例子。我们定义 Blob 类用于保存原始的二进制数据块。Blob 需要自己分配空间以存储数据,因此它需要实现 destructor, copy constructor 和 copy assignment operator。所有的 constructor 和 destructor 都会调用 logging 函数,让我们能看出它们的调用顺序。

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849class Blob {public: Blob() : data_(nullptr), size_(0) { log("Blob's default constructor"); } explicit Blob(size_t size) : data_(new char[size]), size_(size) { log("Blob's parameter constructor"); } ~Blob() { log("Blob's destructor"); delete[] data_; } Blob(const Blob& other) { log("Blob's copy constructor"); data_ = new char[other.size_]; memcpy(data_, other.data_, other.size_); size_ = other.size_; } Blob& operator=(const Blob& other) { log("Blob's copy assignment operator"); if (this == &other) { return *this; } delete[] data_; data_ = new char[other.size_]; memcpy(data_, other.data_, other.size_); size_ = other.size_; return *this; } void set(size_t offset, size_t len, const void* src) { len = min(len, size_ - offset); memcpy(data_ + offset, src, len); }private: char* data_; size_t size_; void log(const char* msg) { cout


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3