如何正确初始化类值成员?

时间:2009-12-06 14:06:12

标签: c++

我们可以说:

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引用。

6 个答案:

答案 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 ++只允许删除(删除)复制构造函数操作以进行返回值优化以及抛出和接收异常对象。

更新2

我又错了?显然我无法正确阅读标准文档?

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)

  1. 不,你想要它的方式*。
  2. 如果将此值作为值而不是引用传递,则将调用Bar()上的复制构造函数,这会产生性能影响(取决于复制构造函数的复杂程度)。
  3. *注意:你拥有它的方式,你不能做new Foo(Bar()),因为你不能传递对Bar()的引用,你需要在变量中有一个对象实例。