簡體   English   中英

如何正確初始化類值成員?

[英]How to properly initialize class value member?

讓我們說我們有這個:

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參考。

出於引用所討論的目的,“如果表達式與成員變量x_的類型相同”,即使b是Bar引用而m_bar是Bar對象,也是如此。

按值取參數可能會更慢,因為如果有的話,它可能會導致額外的副本。

正如Josef和SoapBox所說,引用應該是const。 否則你無法通過臨時工。 擁有一個修改其參數的構造函數是合法的,但它至少是一個好主意,至少在C ++ 0x移動語義到達之前。

我會按照您的要求將其分解為性能和語義差異:

構造函數應該更好地將參數作為值而不是引用嗎?

除非是原始類型或小結構,否則它應該具有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這個值,它可能會選擇忽略(刪除)此調用完全。 但是我們總是可以讓編譯器的生活更輕松,而不是進行額外的調用。

請注意,如果要初始化的對象沒有默認構造函數,則必須在初始化列表中初始化它。 這是另一種語義差異。

如果傳遞值,則在將參數傳遞給構造函數時會生成副本,並在初始化成員時生成第二個副本。 使用引用刪除副本。 但是,您應該使用const引用。 引用會阻止您調用Foo(Bar()) ,因為臨時對象不能綁定到非const引用。

你應該通過const引用傳遞它,因為這將避免不必要的副本。 但是,引用是const很重要,否則編譯器必須期望構造函數可以修改該對象。

如果你通過非const引用傳遞參數,如在你的例子中,根據C ++標准不允許使用新的Foo(Bar())語句(你不能對r值采用非const引用 - 那個是,在作業的右側有些東西)。 一些編譯器無論如何都會允許它,但這是非標准的。

允許C ++刪除副本。 這是在返回值優化中完成的。

更新!

事實證明我錯了!

我在某一方面是正確的,但是C ++委員會在1997年否認了編譯器的優化。無論我記得哪個編譯器,這都是過時的,或做錯了,或者可能決定忽略委員會。 當然看不出任何額外副本的原因!)

在更新的摘要中,只允許C ++刪除(刪除)復制構造函數操作以進行返回值優化以及拋出和接收異常對象。

更新2

我錯了嗎? 顯然我無法正確閱讀標准文檔?

GCC和Microsoft cl.exe都會生成結果而無需額外的臨時副本。

這是我的測試代碼,全部是微軟的:

#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
  1. 不,你想要它的方式*。
  2. 如果將其作為值而不是引用傳遞,則將調用Bar()上的復制構造函數,這將具有性能影響(取決於復制構造函數的復雜程度)。

*注意:你擁有它的方式,你不能做new Foo(Bar())因為你不能傳遞對Bar()的引用,你需要在變量中有一個對象實例。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM