You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++函数调用中如何保留引用?无需new/delete的实现方案

C++引用失效问题与解决方案

问题场景

在baz()函数中,局部Foo对象会随函数返回销毁,但Bar对象仍持有该Foo的引用,导致后续引用失效;而其他场景中Foo的引用能维持到Bar销毁。需求如下:

  1. 不使用new/delete,确保类内引用生命周期与类一致;
  2. 支持传递默认对象(如nofoo)或自定义对象,避免派生带来的内存开销,实现类容器的行为;
  3. 疑惑此类场景下引用的意义,不想用指针(需判空)或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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 22:10:43