临时对象的地址是否始终与 C++ 中将分配给它的对象的地址相同?

Is the address of a temporary object always the same as the address of the object it will be assigned to in C++?

提问人:Zhang Yuhan 提问时间:6/12/2023 更新时间:6/12/2023 访问量:92

问:

在 C++ 中,假设我有一个名为 的 C++ 类,并在其中定义了一个变量。在构造函数中,有一个指令。现在,让我们考虑以下赋值初始化: .我的主要问题是是否总是正确的。AA* ptr;ptr = thisA a = A()a.ptr == &a

我主要关心的是与此赋值有关,它首先创建一个临时对象,然后使用移动构造函数(可能是复制构造函数,我不完全确定)。我想知道临时对象中成员的值是否总是与它将被分配到的对象的地址相同(即,临时对象的地址是否总是与 的地址相同)。ptra

我进行了似乎证实了这种行为的实验,但我想知道 C++ 标准是否保证了此功能。

C++ 指针 构造函数 copy temporary-objects

评论

5赞 463035818_is_not_an_ai 6/12/2023
在不是赋值。A a = A()=
1赞 463035818_is_not_an_ai 6/12/2023
相关新闻 / dupe stackoverflow.com/questions/12953127/...
1赞 Jarod42 6/12/2023
显示代码比描述代码更清晰。
0赞 Thomas Matthews 6/13/2023
不能保证局部变量始终位于内存中的同一位置(地址)。

答:

-2赞 deribaucourt 6/12/2023 #1

总结您的问题,我们有以下几点:

class A {
    A() {
        ptr = this;
    }
    A* ptr;
}

int main() {
    A a = A();
}

如前所述,该语句生成一个临时对象。运算符将调用复制构造函数。这执行浅层复制,这意味着指针被复制,但指针没有被复制。在这种情况下,这会产生未定义的行为。碰巧你的编译器很聪明,直接重用临时对象,但据我所知,语言并不能保证。在管理指针时,您的类应遵循 3/5/0 规则。A()=

在您的特定情况下,我建议正确指定复制构造函数以更新到新地址。尽管我想您是在具有子对象的更广泛的程序中提出这个问题,因此在您的情况下实现它可能不是那么简单。ptrthis

A(const A& other) {
    ptr = this;
}

按照该规则,您应该以类似的方式定义或删除所有相关运算符(move、构造函数、析构函数)。

编辑:如果使用 C++17 或更高版本,则保证复制省略。请参阅 463035818-is-not-a-number 的评论。因此,您的原始代码在该版本及更高版本中是安全的。

评论

3赞 Fareanor 6/12/2023
这完全是错误的。此处没有副本分配。 等价于 ,仅调用默认构造函数,不涉及复制(特别是不赋值)。A a = A();A a;
0赞 deribaucourt 6/12/2023
它与 C++17 及更高版本等效。但我的答案在以前的 C++ 标准中是正确的。请参阅 stackoverflow.com/questions/12953127/...
0赞 Fareanor 6/12/2023
在链接中,它是关于 RVO(返回值优化)的,它是关于函数的,而不是关于构造函数的。复制省略不涉及,它不是函数调用,而是构造函数调用(我们可以将构造函数视为“特殊”函数,但规则并不相同)。A a = A();
0赞 deribaucourt 6/12/2023
在链接中,查找段落“发生复制省略的其他常见位置是从临时对象构造时”
1赞 Peter 6/12/2023
无论 C++17 与早期标准之间的差异如何,从未涉及赋值。从语义上讲,在 C++17 之前,它构造一个临时操作,然后将该临时复制到 - 这两个操作都涉及构造函数,而不是赋值运算符。早期标准和 C++17 之间的区别在于,在早期标准中,省略该临时是允许的,但不是必需的,而在 C++17 中,省略该临时是强制性的。A a = A();Aa
3赞 chi 6/12/2023 #2

当您已经构造了两个对象,并且想要用另一个对象的值覆盖另一个对象时,就会发生赋值。如果现在正在构造一个对象,则称为初始化,并且不会对该对象进行赋值。

A a;            // initialization
A b(32);        // initialization
A c = b;        // initialization
b = c;          // assignment
A d = A();      // initialization
A e{42};        // initialization
A *p = nullptr; // initialization (of a pointer)
p = new A();    // assignment of p, initialization of the new A object
*p = d;         // assignment of the A object

在您的情况下,该行执行初始化,因为现在正在构建中。从 C++17 开始,只要表达式是 prvalue,就会就地执行初始化,比如你的 .此处不执行任何复制。创建的临时对象永远不会在其他地方的内存中具体化,然后复制,而是直接在正确的位置创建。A a = A();aA()A()

实际上,在这种情况下,您可以假装不存在临时对象。 您可以等效地编写并获得相同的效果。A a;

0赞 stefaanv 6/12/2023 #3

接下来的代码显示 temp 对象、命名对象和 temp 对象的副本作为函数参数(通过 const 引用传递)。作为命名对象的 temp 对象都显示相同的地址,但复制的临时对象在 ptr 中显示新地址,但显示命名对象的旧地址。

struct A
{
  A* a;
  A() : a(this) {}
};

void fnc(const A& a)
{
  std::cout << "a.ptr " << a.a << " =?= &a " << &a << "\n";
}

int main()
{
  fnc(A());
  A a;
  fnc(a);
  a = A();
  fnc(a);
}

可能的结果

a.ptr 000000743053F7D8 =?= &a 000000743053F7D8
a.ptr 000000743053F6F8 =?= &a 000000743053F6F8
a.ptr 000000743053F7F8 =?= &a 000000743053F6F8