【问题标题】:C++ references and transient objectsC++ 引用和瞬态对象
【发布时间】:2019-07-12 15:14:10
【问题描述】:

我目前正在开发一个特定的嵌入式系统(使用 C++),其中没有太多内存。因此,使用 new/delete 是不可能的。我确实设置了一些内存管理系统,但是,我正在探索一些其他方法来解决某些问题(与内存管理不完全相关,但这在这里并不重要)。搜索时,弹出了一个相当简单的问题,但我仍然不能 100% 确定答案,所以我想听听专家的意见。

我最后的问题实际上是关于 C++ 中的引用,以及瞬态对象的功能。

class Obj
{
    // id is automatically set, starting from 1, step by 1
    public: const int id = ...;

        // other unrelated stuff in the class
};

class Test
{
    public: Obj& obj;
    public: Test(Obj&& o) : obj(o) { }
};

void test()
{
    Obj o1; // id is 1
    Obj o2; // id is 2

    Test t = Obj(); // [*] id of this transient obj is 3

    cout << t.obj.id << endl; // prints 3, as expected?
}

我知道右值引用是左值(因为它们是命名对象,而命名对象总是左值),因此 Test 类没问题并且没有错误(编译错误)。但是,据我所知,瞬态对象 Obj()(标有 *)应该在 ; 之后被销毁。在同一行,所以如果我是正确的,那么之后的测试对象“t”应该对被破坏的对象有一个无效的引用。并且 3 打印实际上是出乎意料的行为,但意外的是它仍然有 3 写入它的内存位置,我在打印它时正在访问它。这是实际发生的事情,还是我缺乏看到/知道的其他事情?是否有某种机制来检测这种情况并延长/延长对象的生命周期,直到引用它的对象也被销毁(在本例中,延长 Obj() 的生命周期直到函数测试结束( ),当 't' 被破坏时)?

【问题讨论】:

  • 这是实际发生的情况吗,[...]? 是的。你只是“幸运”它成功了。
  • 为什么将右值引用作为构造函数参数?在这种情况下,普通的绝对没问题。
  • @Aconcagua 我总是用外部的右值调用构造函数。我知道这看起来很傻,是的,我可以用 new 完成所有这些操作,并使用指针而不是引用,但我不能使用 new(我试图尽可能避免它,所以我正在寻找其他一些解决方案)。
  • @Vlladz 我不是在谈论指针,而是在谈论引用。 Test(Obj&amp; o) : obj(o) { } 也会做同样的事情——甚至早在 r 值引用之前就已经存在了……
  • @Aconcagua 在这种情况下,我不能传递右值,只能传递左值,而且我总是将右值传递给构造函数。例如。如果我切换到您的解决方案,我将无法传递我真正需要的 Obj()。

标签: c++ reference


【解决方案1】:

所谓的“瞬态对象”的标准名称是“临时对象”。

然而,据我所知,瞬态对象 Obj()(标有 *)应该在 ; 之后被销毁。在同一行,所以如果我是正确的,那么之后的测试对象“t”应该对被破坏的对象有一个无效的引用。而3打印其实是出乎意料的行为

你是对的。引用无效。行为未定义。

临时生命周期延长到完整表达式的末尾。除非它被绑定到一个引用并且如果引用的生命周期更长,在这种情况下生命周期会延长到引用的生命周期。

在这种情况下,临时值绑定到作为构造函数参数的引用,其生命周期不会延长超过调用构造函数的完整表达式。


是否有某种机制来检测这种情况并延长/延长对象的生命周期,直到引用它的对象也被销毁(在此示例中,延长 Obj() 的生命周期直到结束函数 test(),当 't' 被破坏时)?

是的!该机制是类成员。与与对象无关的临时对象不同,成员对象的寿命至少与包含它们的对象一样长。

struct Test
{
    Obj obj;
};

Test t{};

【讨论】:

  • 谢谢!当然,构造函数参数,这很有意义!那显然是在烦我。至于类成员,我忘了提及,我确实需要外部人员通过构造函数将参数传递给类(它始终是右值),但是,我不希望使用 copy/ 完成任何复制/移动移动构造函数,所以在这种情况下,这就是我使用引用而不是常规成员的原因。当然,你可能会问我为什么需要这个,这一切都可以用一个简单的 new & 指针来解决,但正如我所说,我不能使用 new。
【解决方案2】:

让我稍微修改一下您的示例。我删除了const 并向Obj 添加了一个析构函数。如您所见,该对象已被销毁。在您的情况下,如果没有析构函数,id 不会被覆盖,但您不能依赖它。

class Obj {
private:
    static int next;
public:
    int id;
    Obj() {
        id = next;
        ++next;
    }
    ~Obj() {
        id = 42;
    }
};
int Obj::next = 1;

class Test {
public:
    Obj& obj;
    Test(Obj&& o) : obj(o) { }
};

void test() {
    Obj o1; // id is 1
    Obj o2; // id is 2
    Test t = Obj(); // id is 3
    cout << t.obj.id << endl; // prints 42
}

[编辑]

最好在Obj 中添加一个复制构造函数并修改Test 以存储给定的常量引用对象的副本。

class Obj {
private:
    static int next;
public:
    int id;
    Obj() {
        id = next;
        ++next;
    }
    Obj(const Obj& x) {
        id = next;
        ++next;
    }
    ~Obj() {
        id = 42;
    }
};
int Obj::next = 1;

class Test {
public:
    Obj obj;
    Test(const Obj& o) : obj(o) {}
};

void test() {
    Obj o1; // id is 1
    Obj o2; // id is 2
    Test t = Obj(); // id is 3
    cout << t.obj.id << endl; // prints 4
}

【讨论】:

    猜你喜欢
    • 2016-06-02
    • 2011-09-24
    • 2019-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多