C++函数调用中如何保留引用?无需new/delete的实现方案
C++引用失效问题与解决方案
问题场景
在baz()函数中,局部Foo对象会随函数返回销毁,但Bar对象仍持有该Foo的引用,导致后续引用失效;而其他场景中Foo的引用能维持到Bar销毁。需求如下:
- 不使用
new/delete,确保类内引用生命周期与类一致; - 支持传递默认对象(如
nofoo)或自定义对象,避免派生带来的内存开销,实现类容器的行为; - 疑惑此类场景下引用的意义,不想用指针(需判空)或
unique_ptr(仍需检查nullptr),寻求类似unique_ptr的引用类型。
示例代码
class Foo { private: int size; int state; int property; float value; public: Foo ( int s ) { size = s; state = 0; property = 0; value = 0.0; print ( "class Foo constructed with size: %i\n", size ); } ~Foo ( ) { print ( "class Foo destructed with size: %i\n", size ); } }; Foo nofoo ( 0 ); class Bar { private: Foo &fooref; public: Bar ( Foo &ref = nofoo ) : fooref ( ref ) { print ( "class Bar constructed with foo: %i, nofoo: %i\n", (Foo *)&fooref, (void *)&ref==(void *)&nofoo ); } ~Bar ( ) { print ( "class Bar destructed with foo: %i\n", (Foo *)&fooref ); } }; Foo faz() { print ( "function faz called\n" ); Foo ret ( 2 ); return ret; // Foo is not destroyed here } Bar baz() { print ( "function baz called\n" ); Foo foo4 ( 4 ); Bar ret ( foo4 ); return ret; // Foo is destroyed here but stays as reference in Bar } int main () { print ( "test code started\n" ); Foo foo ( 1 ); Bar bar ( foo ); print ( "size of foo: %i\n", sizeof(foo) ); print ( "size of bar: %i\n", sizeof(bar) ); Foo fooref = faz(); print ( "size of fooref=faz(): %i\n", sizeof(fooref) ); Bar bar2; print ( "size of bar2: %i\n", sizeof(bar2) ); Bar barref = baz(); print ( "size of barref=baz(): %i\n", sizeof(barref) ); return 0; }
代码输出
test code started class Foo constructed with size: 1 class Bar constructed with foo: -96188384, nofoo: 0 size of foo: 16 size of bar: 8 function faz called class Foo constructed with size: 2 size of fooref=faz(): 16 class Bar constructed with foo: 1977483344, nofoo: 1 size of bar2: 8 function baz called class Foo constructed with size: 4 class Bar constructed with foo: -96188480, nofoo: 0 class Foo destructed with size: 4 size of barref=baz(): 8 class Bar destructed with foo: -96188480 class Bar destructed with foo: 1977483344 class Foo destructed with size: 2 class Bar destructed with foo: -96188384 class Foo destructed with size: 1 class Foo destructed with size: 0
解决方案与解答
1. 确保引用生命周期与Bar一致(不使用new/delete)
核心原则是保证被引用的Foo对象生命周期长于或等于Bar。针对baz()的问题,可采用两种方式:
- 调整作用域:将
Foo对象的创建移到Bar的生命周期覆盖范围内,比如在main()中创建Foo再传入baz():
这样Bar baz(Foo& foo) { print("function baz called\n"); Bar ret(foo); return ret; } // main中调用 Foo foo4(4); Bar barref = baz(foo4);foo4的生命周期完全覆盖barref,引用不会失效。 - 让Bar持有默认值副本:如果需要
Bar自带默认Foo,可在Bar内部内置一个Foo对象,默认构造时绑定到该内部对象,避免依赖全局变量。
2. 支持默认对象+避免派生开销
无需派生即可实现需求,推荐两种方案:
- 内置默认对象:在
Bar中添加一个私有默认Foo,默认构造时绑定到该对象,自定义构造时绑定外部引用:
这种方式既支持外部自定义class Bar { private: Foo default_foo{0}; Foo& fooref; public: Bar(Foo& ref) : fooref(ref) {} Bar() : fooref(default_foo) {} // 其他成员函数... };Foo的引用,默认情况使用内部的default_foo,无派生开销。 - 保留全局默认对象:当前代码的全局
nofoo方案也可行,全局对象生命周期覆盖整个程序,不会出现失效问题,且无需额外内存开销(除全局对象本身)。
3. 引用的意义与替代方案
引用的意义
引用在此场景下的核心价值:
- 避免大对象拷贝,节省内存和性能开销;
- 明确表达
Bar不拥有Foo的所有权,仅使用外部对象; - 语法比指针简洁,无需频繁解引用操作。
但引用的缺陷是无法重新绑定,且必须始终指向有效对象,这也是失效问题的根源。
类似unique_ptr的非空引用类型
C++标准库没有直接提供这类工具,但可以自行封装一个轻量级的非空引用类:
template<typename T> class non_null_ref { private: T* ptr; public: explicit non_null_ref(T& ref) : ptr(&ref) {} non_null_ref() = delete; // 禁止空构造 // 模拟引用行为 T& operator*() const { return *ptr; } T* operator->() const { return ptr; } // 禁止拷贝,模拟独占语义(可选) non_null_ref(const non_null_ref&) = delete; non_null_ref& operator=(const non_null_ref&) = delete; // 允许移动(可选) non_null_ref(non_null_ref&&) = default; non_null_ref& operator=(non_null_ref&&) = default; };
这个封装保证始终指向有效对象,无需判空,语法接近引用,同时比裸引用更安全(避免误绑定临时对象)。
总结
- 避免引用失效的核心是严格控制被引用对象的生命周期,确保其覆盖持有引用的对象;
- 支持默认对象无需派生,可在
Bar内置默认对象或使用全局默认对象; - 自定义
non_null_ref类可实现类似unique_ptr的非空引用语义,规避指针判空的繁琐。
内容的提问来源于stack exchange,提问作者areop-enap
相关产品推荐
相关产品推荐

