1. 程式人生 > >C++基礎教程面向物件(學習筆記6)

C++基礎教程面向物件(學習筆記6)

重疊和委託建構函式

具有重疊功能的建構函式

例項化新物件時,C ++編譯器會隱式呼叫該物件的建構函式。具有多個具有重疊功能的建構函式的類並不罕見。考慮以下類:

class Foo
{
public:
    Foo()
    {
        // 程式碼A
    }
 
    Foo(int value)
    {
        // 程式碼 A
        // 程式碼B
    }
};

這個類有兩個建構函式:一個預設建構函式和一個帶整數的建構函式。因為建構函式的“程式碼執行A”部分是兩個建構函式都需要的,所以程式碼在每個建構函式中都是重複的。

正如你(希望)現在所學到的那樣,儘可能避免重複程式碼,所以讓我們來看看解決這個問題的一些方法。

顯而易見的解決方案在C ++ 11之前不起作用

顯而易見的解決方案是讓Foo(int)建構函式呼叫Foo()建構函式來執行A部分。

    class Foo
    {
    public:
        Foo()
        {
            // 程式碼 A
        }
     
        Foo(int value)
        {
            Foo(); // 使用上面的建構函式來做A部分程式碼(不能工作)
            // 程式碼 B
        }
    };
``
要麼

class Foo { public: Foo() { // 程式碼 A }

Foo(int value): Foo() // 使用上面的建構函式來做A程式碼( C++11之前的編譯器不能工作)
{
    // 程式碼 B
}

};


但是,使用pre-C ++ 11編譯器,如果你試圖讓一個建構函式呼叫另一個建構函式,它通常會編譯,但它不會像你期望的那樣工作,你可能會花很長時間試圖弄清楚為什麼,即使使用偵錯程式。

(說明:在C ++ 11之前,從另一個建構函式顯式呼叫建構函式建立一個臨時物件,使用建構函式初始化臨時物件,然後丟棄它,保持原始物件不變)

使用單獨的功能

建構函式都可以呼叫非建構函式的類。請注意,非建構函式使用的任何成員都已初始化。雖然您可能想要將程式碼從第一個建構函式複製到第二個建構函式中,但是使用重複的程式碼會使您的類更難理解並且維護起來更加繁重。這個問題的最佳解決方案是建立一個非建構函式,它執行常見的初始化,並讓兩個建構函式都呼叫該函式。

鑑於此,我們可以將上面的類更改為以下類:

class Foo { private: void DoA() { // 程式碼 A }

public: Foo() { DoA(); }

Foo(int nValue)
{
    DoA();
    // 程式碼 B
}

};


通過這種方式,程式碼重複保持最小化。
相應地,您可能會發現自己處於要編寫成員函式以將類重新初始化為預設值的情況。因為您可能已經有一個建構函式來執行此操作,您可能會嘗試從您的成員函式中呼叫建構函式。但是,嘗試直接呼叫建構函式通常會導致意外行為。許多開發人員只是在初始化函式中複製建構函式中的程式碼,這會起作用,但會導致程式碼重複。在這種情況下,最好的解決方案是將程式碼從建構函式移動到新函式,並讓建構函式呼叫您的函式來執行初始化資料的工作:

class Foo { public: Foo() { Init(); }

Foo(int value)
{
    Init();
    // 做有一些事情
}

void Init()
{
    // 初始化程式碼 Foo
}

}; 包含一個Init()函式是很常見的,它將成員變數初始化為預設值,然後讓每個建構函式在執行特定於引數的任務之前呼叫Init()函式。這樣可以最大限度地減少程式碼重複,並允許您從任何地方顯式呼叫Init()。

小警告:使用Init()函式和動態分配記憶體時要小心。因為任何人都可以隨時呼叫Init()函式,所以在呼叫Init()時可能已經或可能沒有分配動態分配的記憶體。小心處理這種情況 - 它可能會有點混亂,因為非空指標可能是動態分配的記憶體或未初始化的指標!

在C ++ 11中委託建構函式

從C ++ 11開始,現在允許建構函式呼叫其他建構函式。此過程稱為委託建構函式(或建構函式連結)。

要讓一個建構函式呼叫另一個建構函式,只需在成員初始化列表中呼叫建構函式即可。這是直接呼叫另一個建構函式的一種情況。應用於上面的示例:

class Foo
{
private:
 
public:
    Foo()
    {
        // 程式碼o A
    }
 
    Foo(int value): Foo() // 用Foo() 預設建構函式完成A
    {
        // 程式碼B
    }
 
};

這完全符合您的預期。確保從成員初始值設定項列表中呼叫建構函式,而不是在建構函式的主體中。 這是使用委託建構函式來減少冗餘程式碼的另一個示例:

#include <string>
#include <iostream>
using namespace std;
class Employee
{
private:
	int m_id;
	std::string m_name;

public:
	Employee(int id = 0, const std::string &name = "") :
		m_id(id), m_name(name)
	{
		std::cout << "Employee " << m_name << " created.\n";
	}

	// 使用委派建構函式來最小化冗餘程式碼
	Employee(const std::string &name) : Employee(0, name)		//如果是委託建構函式,這裡是不能在初始化其他的變數的
	{ }

	void print();
};
void Employee::print()
{
	cout << "myID:" << m_id <<endl<< "myname:" << m_name << endl;
}
int main()
{
	Employee Joe("mawei");
	Joe.print();
	return 0;
}

這個類有2個建構函式,其中一個委託給Employee(int,const std :: string&)。通過這種方式,冗餘程式碼的數量被最小化(我們只需要編寫一個建構函式體而不是兩個)。

關於委託建構函式的一些附加說明。首先,不允許委託給另一個建構函式的建構函式進行任何成員初始化。所以你的建構函式可以委託或初始化,但不能同時委託或初始化。

其次,一個建構函式可以委託給另一個建構函式,該建構函式委託給第一個建構函式。這形成了一個無限迴圈,並將導致程式耗盡堆疊空間並崩潰。您可以通過確保所有建構函式都解析為非委託建構函式來避免這種情況。