C++中能否在类成员方法内将当前对象指向另一个对象?
嘿,这个问题问到点子上了——对于体积庞大的对象来说,全量拷贝的性能开销确实是个棘手的问题。咱们先把你的疑问拆解清楚,再给出可行的方案:
为什么*this = tmp有问题?
首先,这段代码存在语法错误:tmp是BigClass*类型的指针,而*this是BigClass对象实例。如果你的赋值运算符重载是针对BigClass对象(比如参数是const BigClass&),直接用指针赋值根本编译不过,正确的写法应该是*this = *tmp——但这就会触发你重载的赋值运算符,也就是逐个拷贝所有成员变量,这正是你想避免的高开销操作。
另外,你想直接让this指向tmp的对象也是行不通的:在C++的成员函数里,this是一个const指针(类型为BigClass* const,非const成员函数中),它的指向是固定的,用来指代当前调用成员函数的对象,你无法修改this本身的指向。
可行的解决方案
1. Pimpl(指针实现)惯用法——完全避免拷贝
这是处理大对象最常用的优化手段之一:把所有体积大的成员变量封装到一个内部的Impl结构体里,然后外层的BigClass只持有一个指向Impl的智能指针。替换对象时,只需要替换这个指针即可,完全不需要拷贝大成员。
示例代码:
// BigClass.h #include <memory> class BigClass { struct Impl; // 前向声明 std::unique_ptr<Impl> pImpl; public: BigClass(); ~BigClass(); // 需要在cpp文件中定义,因为Impl是不完全类型 void replace(); }; // BigClass.cpp #include "BigClass.h" #include <vector> struct BigClass::Impl { int a; long b; std::vector<int> c; // 其他体积大的成员变量... }; BigClass::BigClass() : pImpl(std::make_unique<Impl>()) {} BigClass::~BigClass() = default; void BigClass::replace() { auto tmpImpl = std::make_unique<Impl>(); // 对tmpImpl执行计算逻辑,比如 tmpImpl->a = 100; tmpImpl->c.push_back(...); // 替换内部指针,无任何大对象拷贝 pImpl.swap(tmpImpl); // 或者直接 pImpl = std::move(tmpImpl); }
这种方式下,replace方法的开销几乎可以忽略,只是交换了两个智能指针的指向,原来的Impl对象会被自动销毁。
2. 使用智能指针管理对象生命周期
如果你不想修改BigClass的内部结构,可以直接用智能指针来持有对象实例,替换时直接更新指针指向即可:
#include <memory> class BigClass { int a; long b; std::vector<int> c; // 其他成员... public: // 可以提供一个静态方法来创建并初始化新对象 static std::unique_ptr<BigClass> createAndCompute() { auto tmp = std::make_unique<BigClass>(); // 对tmp执行计算逻辑 return tmp; } }; // 使用方式 int main() { auto foo = std::make_unique<BigClass>(); // 需要替换对象时 foo = BigClass::createAndCompute(); // 仅转移智能指针所有权,无拷贝 return 0; }
这种方式同样没有任何大对象拷贝,只是转移了智能指针的所有权,性能开销极小。
3. 移动语义——最小化拷贝开销
如果以上两种方案都不适合你,可以给BigClass实现移动赋值运算符,把临时对象的资源直接转移给当前对象,而不是拷贝。对于std::vector这类支持移动语义的容器来说,移动操作只是转移内部的指针,不会拷贝数据:
#include <vector> #include <utility> // 用于std::move class BigClass { int a; long b; std::vector<int> c; // 其他成员... public: // 移动赋值运算符 BigClass& operator=(BigClass&& src) noexcept { if (this != &src) { // 基本类型直接拷贝(开销极小) a = src.a; b = src.b; // 容器类型使用移动语义,转移资源 c = std::move(src.c); // 其他自定义大成员也用std::move转移 } return *this; } void replace() { BigClass tmp; // 直接创建临时对象,不用指针 // 对tmp执行计算逻辑 *this = std::move(tmp); // 调用移动赋值,转移资源 } };
这种方式虽然会有少量基本类型的拷贝,但大部分大资源都是通过移动转移的,性能比全量拷贝提升非常明显。
内容的提问来源于stack exchange,提问作者user12224961

