如何延长局部变量的生命周期或使用引用的正确方法是什么

How to extend lifetime of the local variable or what is right way to use references

我正在开发一些 class 并遇到了这个问题。 考虑一下我有以下 class:

struct A
{
    int *p;
    A() 
    {
        p = new int(1); 
        cout << "ctor A" << endl; 
    }
    A(const A& o) 
    { 
        cout << "copy A" << endl;
        p = new int(*(o.p));
    }
    A(A&& o) 
    {
        cout << "move A" << endl;
        p = std::move(o.p);
        o.p = NULL;
    }
    A& operator=(const A& other)
    {       
        if (p != NULL)
        {
            delete p;
        }
        p = new int(*other.p);
        cout << "copy= A" << endl;
        return *this;
    }
    A& operator=(A&& other)
    {       
        p = std::move(other.p);
        other.p = NULL;
        cout << "move= A" << endl;
        return *this;
    }
    ~A()
    {
        if(p!=NULL)
            delete p;
        p = NULL;
        cout << "dtor A" << endl;
    }
};

然后是 class,其中 A 作为 属性:

class B {
public:
  B(){}
  A myList;
  const A& getList() { return myList; };
};

这个函数在不同的情况下检查一些变量值和 returns 不同的对象:

B temp;
A foo(bool f)
{
    A a;
    *a.p = 125; 
    if (f)
        return a;
    else
    {
        return temp.getList();
    }
}

现在,我想像这样使用这个功能:

A list1 = foo(true);
if(list1.p != NULL)
    cout << (*list1.p) << endl;

cout << "------"<<endl;
A list2 = foo(false);
if (list2.p != NULL)
    cout << (*list2.p) << endl;

这种情况的目的是:

如果参数为 true,函数 foo 应该 return(或移动)而不复制在 p 中更改的某些本地对象,或者应该 return全局变量 temp 的 属性 不调用 A 的复制构造函数(即 myList 的 return 引用),也不应该从 myList 中获取 myList B(它不应该从 B 中破坏 myList,因此不能使用 std::move)如果参数是 false

我的问题是:

我应该如何更改函数 foo 以遵循更高的条件? foo 的当前实现在 true 情况下可以正常工作并移动该局部变量,但在 false 情况下它会调用 list2 的复制构造函数。其他想法是以某种方式延长局部变量的生命周期,但添加 const 引用对我不起作用。当前输出为:

ctor A
ctor A
move A
dtor A
125
------
ctor A
copy A
dtor A
1
dtor A
dtor A
dtor A

如果你能把B改成

class B {
public:
  B(){}
  std::shared_ptr<A> myList = std::make_shared<A>();
  const std::shared_ptr<A>& getList() const { return myList; };
};

那么foo可以是:

B b;
std::shared_ptr<A> foo(bool cond)
{
    if (cond) {
        auto a = std::make_shared<A>();
        *a->p = 125; 

        return a;
    } else {
        return b.getList();
    }
}

Demo

输出是

ctor A
ctor A
125
------
1
dtor A
dtor A

你的函数 foo return 是 A 的实例,不是引用(也不是指针),所以你不复制或移动就无法访问 B.myList 内容。

为了获得此访问权限,您应该使用智能指针(如 Jarod42 所写)或像这样的简单指针:

B temp;
A* foo(bool f)
{
    if (f)
    {
        A* ptr = new A;
        *ptr->p = 125;
        return ptr;
    }    
    else
    {
        return &temp.getList();
    }
}

但是这个特定的代码将不起作用因为 .getList() returns const 引用但是 foo returns 非常量指针(这可以但不应该被 const_cast<> 脏黑)。

通常您需要选择 foo 函数应该 return:

  • A 的新实例class 具有特定数据
  • 访问现有实例

如果您必须在运行时做出这个决定(例如通过您的 bool 参数),那么指针(简单或智能 - 无论如何)是唯一的选择(还记得删除手动分配的内存)。

最简单的解决方案可能是使用 Jarod42 的回答中的 std::shared_ptr。但是如果你想避免使用智能指针,或者如果你不能更改 B,你可以创建自己的包装器 class,它可能拥有也可能不拥有 Astd::optional 可能很方便:

class AHolder {
  private:
    std::optional<A> aValue;
    const A& aRef;
  public:
    AHolder(const A& a) : aRef(a) {}
    AHolder(A&& a) : aValue(std::move(a)), aRef(aValue.value()) {}
    const A* operator->() const { return &aRef; }
};

如果需要,class 包含一个 optional 来拥有 A,您可以使用移动语义将其移入。class 还包含一个引用(或指针)引用包含的值或引用另一个对象。

您可以 return 来自 foo:

AHolder foo(bool f)
{
    A a;
    *a.p = 125; 
    if (f)
        return a;
    else
    {
        return temp.getList();
    }
}

并且调用者可以访问包含的引用:

  auto list1 = foo(true);
  if(list1->p != nullptr)
    cout << (*list1->p) << endl;

  cout << "------"<<endl;
  auto list2 = foo(false);
  if (list2->p != nullptr)
    cout << *list2->p << endl;

Live demo.

如果您无法访问 std::optional,则可以使用 boost::optional,或者您可以使用 std::unique_ptr,但要付出动态内存分配的代价。