移动std::unique_ptr前复制std::function成员是否合法?有无未定义行为?
现有类继承体系如下:
class BaseICannotChange {}; class DerivedIControl: public BaseICannotChange { private: int _Value; public: DerivedIControl(int value): _Value{value} {} int getValue () const {return _Value;} };
我持有一个指向DerivedIControl的std::unique_ptr,希望在指针被移动后仍能获取其值(已确定底层对象仍存在)。原代码中移动指针后再用原指针访问会崩溃:
int main () { auto myDerived = std::make_unique<DerivedIControl>(42); std::cout << "My value is: " << myDerived->getValue() << std::endl; std::unique_ptr<BaseICannotChange> myBase = std::move(myDerived); // 此处会崩溃,因为myDerived已被移动 // std::cout << "My value is: " << myDerived->getValue() << std::endl; return 0; }
由于我可以修改DerivedIControl,我尝试将getter改为可复制的std::function成员,修改后的类:
class BaseICannotChange {}; class DerivedIControl: public BaseICannotChange { private: int _Value; public: DerivedIControl(int value): _Value{value} {} std::function<int(void)> getValue = [this] () {return _Value;}; };
修改后编写的代码在多个在线编译器上可正常运行:
int main () { auto myDerived = std::make_unique<DerivedIControl>(42); std::cout << "My value is: " << myDerived->getValue() << std::endl; auto myGetter = myDerived->getValue; std::unique_ptr<BaseICannotChange> myBase = std::move(myDerived); std::cout << "My value is: " << myGetter() << std::endl; return 0; }
请问这种修改是否合法?是否存在未被发现的未定义行为隐患?
这种做法存在明确的未定义行为隐患,核心问题出在lambda捕获的this指针上:
lambda的
this捕获本质:类中初始化getValue时,lambda捕获的是当前DerivedIControl对象的this指针。复制std::function时,lambda会被拷贝,但内部持有的this指针始终指向原对象——当前场景下对象被myBase接管所以暂时没问题,但隐患已存在。直接隐患来源:一旦
myBase被销毁(比如离开作用域),myGetter持有的this指针就会变成悬空指针,此时调用myGetter()会触发未定义行为,可能表现为崩溃、输出垃圾值,或看似正常但埋下潜在风险。隐藏风险:如果
DerivedIControl对象本身被移动(而非unique_ptr的移动),lambda中的this指针不会自动更新,依然指向旧对象的内存空间,此时调用getValue或复制后的myGetter都会访问已失效的对象,同样触发未定义行为。
更安全的替代方案
既然你能修改DerivedIControl,结合场景需求,推荐两种稳妥做法:
直接拷贝值:如果
_Value是简单类型(如int),提前存储值的副本即可,无需依赖对象指针:int main () { auto myDerived = std::make_unique<DerivedIControl>(42); int valueCopy = myDerived->getValue(); // 直接拷贝值 std::unique_ptr<BaseICannotChange> myBase = std::move(myDerived); std::cout << "My value is: " << valueCopy << std::endl; }捕获值的lambda:如果需要访问对象的最新值,可提供生成lambda的方法,通过捕获值或对象指针(需严格保证对象生命周期)实现:
class DerivedIControl: public BaseICannotChange { private: int _Value; public: DerivedIControl(int value): _Value{value} {} // 生成捕获值副本的lambda,确保脱离对象也能返回捕获时的值 std::function<int(void)> makeGetValueCopy() { return [val = _Value]() { return val; }; } // 生成捕获值指针的lambda,需保证对象生命周期长于lambda std::function<int(void)> makeGetValueRef() { return [ptr = &_Value]() { return *ptr; }; } };
内容的提问来源于stack exchange,提问作者antonio

