问题 复制/移动省略需要复制/移动构造函数的明确定义


考虑以下程序:

#include <iostream>
#include <utility>

class T {
public:
    T() { printf("address at construction:    %zx\n", (uintptr_t)this); }
    // T(const T&) { printf("copy-constructed\n"); } // helps
    // T(T&&) { printf("move-constructed\n"); }      // helps
    // T(const T&) = default;                        // does not help
    // T(T&&) = default;                             // does not help
};

T f() { return T(); }

int main() {
    T x = f();
    printf("address after construction: %zx\n", (uintptr_t)&x);
    return 0;
}

用。编译 g++ -std=c++17 test.cpp 给出以下输出(与...相同) clang++):

address at construction:    7ffcc7626857
address after construction: 7ffcc7626887

基于 C ++参考 我希望程序输出两个相同的地址,因为应该保证复制/移动被省略(至少在C ++ 17中)。

如果我显式定义了复制或移动构造函数或两者(参见示例中注释掉的行),程序会给出预期的输出(即使使用C ++ 11):

address at construction:    7ffff4be4547
address after construction: 7ffff4be4547

只需将复制/移动构造函数设置为 default 没有帮助。

该引用明确指出

[复制/移动构造函数]不需要存在或可访问

那我在这里错过了什么?


8421
2018-04-07 07:39


起源

有关: stackoverflow.com/questions/48879226/... - xskxzr


答案:


因为这是一个特殊情况,复制省略可能不适用。

引自 [class.temporary]第3段

当类类型X的对象传递给函数或从函数返回时,如果X的每个复制构造函数,移动构造函数和析构函数是微不足道的或删除的,并且X至少有一个未删除的副本或移动构造函数,则实现是允许创建一个临时对象来保存函数参数或结果对象。临时对象分别由函数参数或返回值构成, 并且函数的参数或返回对象被初始化,就好像通过使用未删除的普通构造函数来复制临时对象一样 (即使该构造函数不可访问或者不会通过重载决策选择来执行对象的复制或移动)。 [注意:允许此纬度允许类类型的对象传递给寄存器中的函数或从寄存器中的函数返回。 - 结束说明]


9
2018-04-07 08:07



有趣的是,在我看来,虽然该标准过于宽泛地规定了这个例外。我的问题的动机是我的 T 相当大,我的堆栈非常小,所以我非常关心不重复堆栈上的对象。如果这种模式多次出现,那么为每个类添加一个显式的复制构造函数就像是一个hack,有什么我可以做的吗 f() 避免这个? - Al Gebra
@AlGebra - 但你的 T 这里不大,它非常小。在寄存器中构造并按值返回的非常小的对象可能根本不需要任何堆栈空间。它可能是 更高效 比涉及RVO机器,至少需要一些间接。 - Bo Persson
@AlGebra:停止猜测你的编译器。如果你的 实际  T 真的是“相当大”,那么编译器就不会执行这种优化。所以让你的编译器完成它的工作,这样你就可以回去完成你的工作了:编写一个正常运行的程序。 - Nicol Bolas
@BoPersson @NicolBolas感谢您指出这一点。添加时 const int data[5] = {0}; 至 T 编译器确实忽略了这一举措。然而,在我的实际用例中,对象实际上是在堆栈上重复的,这就是我首先调查它的原因。在隔离问题时,我一定错过了其他一些相关细节。 - Al Gebra