简体   繁体   中英

How to properly initialize class value member?

lets say we have this:

class Foo {

    public:
    Foo(const Bar& b) : m_bar(b) {}

    private:
    Bar m_bar;
};

Now regarding efficiency C++ FAQ LITE says this:

Consider the following constructor that initializes member object "x" using an initialization list: Fred::Fred() : x(whatever) { }. The most common benefit of doing this is improved performance. For example, if the expression whatever is the same type as member variable "x", the result of the whatever expression is constructed directly inside "x" — the compiler does not make a separate copy of the object.

Should the constructor better have the parameter as value instead of reference?

Since I am using the constructor initialization list, would there be a performance difference?

Finally, and most important , would there be a semantic difference? eg, to the caller of new Foo(Bar())

Thank you

-- Edit -- Corrected the parameter declaration as const reference.

For the purposes of what your quote is talking about, "if the expression whatever is the same type as member variable x_" is true even if b is a Bar reference and m_bar is a Bar object.

Taking the parameter by value could be slower, since if anything it's likely to result in an extra copy.

As Josef and SoapBox say, the reference should be const. Otherwise you can't pass temporaries. It's legal to have a constructor which modifies its argument, but it's very rarely a good idea, at least until C++0x move semantics arrive.

I'll break it down into performance and semantic differences, as you requested:

Should the constructor better have the parameter as value instead of reference?

Unless it is a primitive type or small struct, it should have the parameter passed by const reference. Passing by reference gives you a performance difference: the program does not have to make a copy of the whole object before passing it to the constructor. For small objects, however, the time saved by avoiding the copy may not be offset by the extra level of indirection.

Passing by const ensures that you can call the constructor with temporary objects. Thus, to the caller of the constructor, there is no semantic difference as to whether you are calling by value or by const reference.

Since I am using the constructor initialization list, would there be a performance difference?

If you do not use the initialization list, and the object you are initializing has a default constructor, this is what happens semantically :

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
    }
};

However, if the compiler is able to see that the default constructor of Bar has no side effects other than to initialize bar , and that this value of bar is overwritten in the body of the constructor, it may choose to elide (remove) this call completely. But we can always make life easier for the compiler and not make that extra call.

Note that if the object you want to initialize does not have a default constructor, then you must initialize it in the initialization list. This is another semantic difference.

If you pass by value, a copy is made when passing the argument to the constructor, and a second copy is made when the member is initialized. Using a reference removes a copy. You should use a const reference, however. A reference would prevent you from calling Foo(Bar()) , because temporary objects can't be bound to non-const references.

You should pass it by const reference, since this will avoid an unnecessary copy. It is important that the reference is const, however, or else the compiler must expect that the object can be modified by the constructor.

If you pass the argument by non-const reference, as in your example, the statement new Foo(Bar()) is not allowed according to the C++ standard (you cannot take a non-const reference to an r-value -- that is, something that would be on the right hand side of an assignment). Some compilers will allow it anyway, but that is non-standard.

C++ is allowed to remove copies. This is done in the return value optimization.

Update!

It turns out that I am wrong!

I was right at one point, but the C++ committee denied compilers the optimization in 1997. Whichever compiler I was remembering that did this was out of date, or doing it wrong, or possibly decided to ignore the committee. ( I certainly can't see any reason for extra copies!)

In the updated summary, C++ is only allowed to elide (remove) a copy constructor operation for the return value optimization and for throwing and receiving exception objects.

Update 2

I am wrong again? Apparently I cannot read standards documentation properly?

GCC and Microsoft cl.exe both produce results without extra copies for temporaries.

Here is my test code, all 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;
}

Use this to replace the includes for de-Microsofting for GCC:

#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

Here is the output, same for both compilers:

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
  1. No, you want it the way you have it*.
  2. If you pass this as a value instead of a reference, the copy constructor on Bar() will be called, which would have performance implications (depending on how complicated the copy constructor is).

*Note: The way you have it, you cannot do new Foo(Bar()) because you can't pass a reference to Bar(), you need to have an object instance in a variable for that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM