我想知道我的建造者的最佳状态。下面是一些示例代码:
class Y { ... }
class X
{
public:
X(const Y& y) : m_y(y) {} // (a)
X(Y y) : m_y(y) {} // (b)
X(Y&& y) : m_y(std::forward<Y>(y)) {} // (c)
Y m_y;
}
Y f() { return ... }
int main()
{
Y y = f();
X x1(y); // (1)
X x2(f()); // (2)
}
据我所知,这是编译器在每种情况下所能做的最好的。
(1a)y复制到x1.m_y(1个副本)
(1b)y复制到X的构造函数的参数中,然后复制到x1.m_y(2份)
(1c)y移动到x1.m_y(1次移动)
(2a)f()的结果被复制到x2.m_y(1个拷贝)
(2b)f()被构造到构造函数的参数中,然后复制到x2.m_y(1个副本)
(2c)f()在堆栈上创建,然后移动到x2.m_y(1次移动)
现在有几个问题:
1.在这两个方面,通过const引用传递并不比通过值传递更差,有时甚至比通过值传递更好。这似乎违背了关于"Want Speed? Pass by Value."的讨论。对于C++(不是C0x),我应该坚持使用const引用传递这些构造函数,还是应该通过值传递?对于C0x,我应该通过右值引用传递而不是通过值传递吗?
1.对于(2),我更希望临时直接构造成x.m_y。即使是右值版本,我认为也需要移动,除非对象分配动态内存,否则这与复制一样多。有没有什么方法可以编写这样的代码,以便允许编译器避免这些复制和移动?
1.我在我认为编译器能做得最好的方面和我的问题本身都做了很多假设。如果这些错误,请更正。
1条答案
按热度按时间xxb16uws1#
我随便举了几个例子。我使用了GCC 4.4.4。
-std=c++0x
**首先,我将一个非常简单的示例与两个分别接受
std::string
的类放在一起。该程序在
stdout
上的输出如下:A
或B
。* 这与C++ FAQ一致。基本上,GCC可能(并且愿意)生成代码来构造a, a2, b, b2
* 就地 *,即使调用一个外观上是按值返回的函数。因此,GCC可以避免许多可能通过查看代码“推断”其存在的临时变量。接下来我们要看的是在上面的例子中
std::string
实际上被复制的频率。让我们把std::string
替换为我们可以更好地观察和看到的东西。-std=c++0x
*不幸的是,输出符合预期:
B
的构造函数创建的临时S
。使用S
的默认复制构造函数并没有改变这一点。将f, g
更改为确实具有预期效果。看起来GCC愿意在适当的位置构造
B
的构造函数的参数,但不愿意在适当的位置构造B
的成员。请注意,仍然没有创建临时A
或B
。这意味着a, a2, b, b2
仍在建造中 * 在原地 *。很酷现在让我们研究新的移动语义如何影响第二个例子。
-std=c++0x
**考虑在S
中添加以下构造函数:将
B
的构造函数改为并将
g()
和g2()
更改为我们得到这个输出
因此,我们能够通过使用rvalue传递将 * 四个副本 * 替换为 * 两个移动 *。
回想一下更新后的
g()
和g2()
,标记的位置显示问题。在非临时对象上执行了移动。
s
将处于有效但未指定的状态。因为右值引用的行为与左值引用类似,只是它们绑定到右值,所以我们不得不将s
强制转换为右值,以便将其传递给B
的构造函数,该构造函数现在接受右值引用。所以我们一定不要忘记用一个常量左值引用的构造函数重载B
的构造函数。然后你会注意到,这两个
g, g2
都会导致“constructor2”被调用,因为符号s
在任何情况下都更适合const引用而不是右值引用。我们可以通过以下两种方式之一说服编译器在g
中执行移动:f
更改为只有当
A
的赋值提供时,它才能满足strong guarantee。无法跳过到result
的复制,也无法构建tmp
来代替result
,因为result
尚未构建。因此,它比以前慢,在那里不需要复制。C ++0x编译器和移动赋值操作符会减少开销,但它仍然比按值返回慢。按值回报更容易提供强有力的保障。该对象在适当位置构造。如果其中的一部分失效,而其他部分已经构造完毕,则正常的展开将被清理干净,只要
S
的构造器满足了关于其自身成员的基本保证和关于全局项的强保证,整个按值返回过程实际上提供了强保证。但总是喜欢这个
swap
,无论如何考虑通过值传递(C ++0x之前)**如果你有便宜的默认构造,与swap
* 结合可能 * 比复制东西更有效。假设S
的构造函数为