我们可以说:
class Foo {
public:
Foo(const Bar& b) : m_bar(b) {}
private:
Bar m_bar;
};
现在关于效率C++ FAQ LITE说:
考虑以下使用初始化列表初始化成员对象“x”的构造函数:Fred :: Fred():x(无论如何){}。这样做的最常见好处是提高了性能。例如,如果表达式与成员变量“x”的类型相同,那么任何表达式的结果都直接在“x”内构造 - 编译器不会创建该对象的单独副本。
构造函数应该更好地将参数作为值而不是引用吗?
由于我使用的是构造函数初始化列表,是否会有性能差异?
最后,和最重要的,会有语义差异吗?例如,对新Foo的调用者(Bar())
谢谢
- 编辑 - 将参数声明更正为const引用。
答案 0 :(得分:4)
出于引用所讨论的目的,“如果表达式与成员变量x_的类型相同”即使b
是条形参考并且m_bar
是条形图也是如此对象
按参数取值可能会更慢,因为如果有的话,它可能会产生额外的副本。
正如Josef和SoapBox所说,引用应该是const。否则你无法通过临时工。拥有一个修改其参数的构造函数是合法的,但它至少是一个好主意,至少在C ++ 0x移动语义到达之前。
答案 1 :(得分:3)
我会按照您的要求将其分解为性能和语义差异:
构造函数应该更好地将参数作为值而不是引用吗?
除非是原始类型或小结构,否则它应该具有const引用传递的参数。通过引用传递给你一个性能差异:程序在将它传递给构造函数之前不必复制整个对象。但是,对于小型对象,避免复制所节省的时间可能无法通过额外的间接级别来抵消。
通过 const 传递可确保您可以使用临时对象调用构造函数。因此,对于构造函数的调用者,您是按值还是通过const引用进行调用没有语义差异。
由于我使用的是构造函数初始化列表,是否会有性能差异?
如果您不使用初始化列表,并且您正在初始化的对象具有默认构造函数,那么语义会发生这种情况:
class Foo
{
Bar bar;
Foo(const &Bar bar_)
/* bar(Bar()) is implicitly called here,
before the start of the function body */
{
/* Note that we cannot do bar(bar_) now
as bar has already been constructed.
So we might do this instead: */
bar = bar_; // the assignment operator function is called here
}
};
但是,如果编译器能够看到Bar
的默认构造函数除了初始化bar
之外没有副作用,并且该bar
的值被覆盖了构造函数的主体,它可以选择完全删除(删除)此调用。但是我们总是可以让编译器的生活更轻松,而不是进行额外的调用。
请注意,如果要初始化的对象没有默认构造函数,那么必须在初始化列表中初始化它。这是另一种语义差异。
答案 2 :(得分:2)
如果传递值,则在将参数传递给构造函数时会生成副本,并在初始化成员时生成第二个副本。使用引用删除副本。但是,您应该使用const引用。引用会阻止您调用Foo(Bar())
,因为临时对象不能绑定到非const引用。
答案 3 :(得分:1)
您应该通过const引用传递它,因为这样可以避免不必要的复制。但是,引用是const很重要,否则编译器必须期望构造函数可以修改该对象。
如果你通过非const引用传递参数,如在你的例子中,根据C ++标准,不允许使用语句 new Foo(Bar())(你不能采用非const对r值的引用 - 也就是说,它位于赋值的右侧。一些编译器无论如何都会允许它,但这是非标准的。
答案 4 :(得分:1)
允许C ++删除副本。这是在返回值优化中完成的。
事实证明我错了!
我一度是正确的,但是C ++委员会在1997年否认编译器的优化。无论我记得哪个编译器,这都是过时的,或做错了,或者可能决定忽略委员会。 (我肯定看不出任何额外副本的原因!)
在更新的摘要中,C ++只允许删除(删除)复制构造函数操作以进行返回值优化以及抛出和接收异常对象。
我又错了?显然我无法正确阅读标准文档?
GCC和Microsoft cl.exe都会生成结果而无需额外的临时副本。
这是我的测试代码,全部是Microsofted:
#include <stdio.h>
#include <tchar.h>
class A {
int m;
public:
A(int x=0) : m(x)
{
_putts(_T("Constructor A"));
}
A(const A &x) : m(x.m)
{
_putts(_T("Copy A"));
}
int get() const { return m; }
};
class B {
A m;
public:
B(int y=5) : m(y)
{
_putts(_T("Constructor B"));
}
B(const B &x) : m(x.m)
{
_putts(_T("Copy B"));
}
B(A x) : m(x)
{
_putts(_T("Construct B from A value"));
}
int get() const { return m.get(); }
};
class C {
A m;
public:
C(int y=5) : m(y)
{
_putts(_T("Constructor C"));
}
C(const C &x) : m(x.m)
{
_putts(_T("Copy C"));
}
C(const A &x) : m(x)
{
_putts(_T("Construct C from A reference"));
}
int get() const { return m.get(); }
};
int _tmain(int argc, _TCHAR* argv[])
{
_putts(_T("Hello World"));
int i = 27;
if( argc > 1 )
i = _tstoi(argv[0]);
A aval(i);
_tprintf(_T("A value is: %d\n"), aval.get());
B bval( aval );
_tprintf(_T("Value is: %d\n"), bval.get());
B bval2( (A(i)) );
_tprintf(_T("Value is: %d\n"), bval2.get());
C cval( aval );
_tprintf(_T("Value is: %d\n"), cval.get());
C cval2( (A(i)) );
_tprintf(_T("Value is: %d\n"), cval2.get());
_putts(_T("Goodbye World"));
return 0;
}
使用此替换GCC的de-Microsofting包含:
#include <cstdio>
#include <cstdlib>
using namespace std;
#define _TCHAR char
#define _T(x) x
#define _tmain main
#define _putts puts
#define _tprintf printf
#define _tstoi atoi
这是两个编译器的输出:
Hello World
Constructor A
A value is: 27
Copy A
Copy A
Construct B from A value
Value is: 27
Constructor A
Copy A
Construct B from A value
Value is: 27
Copy A
Construct C from A reference
Value is: 27
Constructor A
Copy A
Construct C from A reference
Value is: 27
Goodbye World
答案 5 :(得分:0)
*注意:你拥有它的方式,你不能做new Foo(Bar())
,因为你不能传递对Bar()的引用,你需要在变量中有一个对象实例。