C++自修入門實境秀、C++ Primer 5版研讀秀 48/ ~v7 類別 7.5.4. Implicit Class-Type Conver...





7.5.2. Delegating Constructors

7.5.2委派建構器

又是一個文言文!

The new standard extends the use of constructor initializers to let us define so-called delegating constructors. A delegating constructor uses another constructor from its own class to perform its initialization. It is said to “delegate” some (or all) of its work to this other constructor.

so-called

一種叫做

so這樣=這種(樣=種)

delegate……to

分配……給

派……給

不必事必躬親

一個委派建構器(delegating constructor)的成員初始化串列(member initializer list)是它所在類別的名稱:

In a delegating constructor, the member initializer list has a single entry that is the name of the class itself. Like other member initializers, the name of the class is followed by a parenthesized list of arguments. The argument list must match another constructor in the class.

可見只要看到一個建構器的初始器串列出現了該類別的名稱,就知道是個委派建構器了

Sales_data(): Sales_data("", 0, 0) {}

而是用哪一個建構器來委派,就由其初始器串列中的引數「數、序及型別」來判斷

頁292

23:00

委派是可以「遞移」的,一個「推」一個, 一個委派給另一個去做,像踢皮球一樣:

The constructor that takes an istream& also delegates. It delegates to the default constructor, which in turn delegates to the three-argument constructor. Once those constructors complete their work, the body of the istream& constructor is run.

建構器都跟初始化很有關係的 31:20

When a constructor delegates to another constructor,當一個建構器委派給另一個建構器來代勞的時候 the constructor initializer list and function body of the delegated-to constructor are both executed. 被委派,就是委託信任,所以被委派的一切工作,都是要被執行的。所以被委派的建構器,其初始器串列(initializer list)和函式本體都是要被執行的。In Sales_data, the function bodies of the delegated-to constructors happen to be empty. Had the function bodies contained code, that code would be run before control returned to the function body of the delegating constructor.

委派建構的執行流程:

委派建構器的初始器串列→被委派的建構器初始器串列→被委派的函式本體程式碼→委派建構器的函式本體程式碼

練習7.41

38:49

1:8:00

Run: 1:59:00

#include "Sales_data.h"

#include <iostream>

using namespace std;

int main() {

Sales_data def();//這樣寫變成了宣告一個會傳回Sales_data型別值的函式了

Sales_data def;//這樣才是宣告一個Sales_data型別的物件,與下式同!

//Sales_data def=Sales_data();

Sales_data oneStr("222-111");

Sales_data delegated_to("333-222",24.0,10.22);

Sales_data fourArg("444-333",32.0,333.3,10);

Sales_data istreamOne(cin);

}

Sales_data obj(); // ok: but defines a function, not an object

if (obj.isbn() == Primer_5th_ed.isbn()) // error: obj is a function(頁293)

.h:



#pragma once

#ifndef SALES_DATA_H

#define SALES_DATA_H

#include<string>

#include<iostream>

using namespace std;

struct Sales_data {

//Sales_data() = default;

Sales_data() :Sales_data("", 0.0, 0.0) { cout<<"I'm 預設建構器"<<endl; }

//Sales_data(const string &bNo) :bookNo{bNo } {}

Sales_data(const string& bNo) :Sales_data("", 0.0, 0.0) { cout << "I'm the take a single string argument one" << endl; }

Sales_data(const string& bNo, const double bSize, const double rvn) :bookNo{ bNo }, bookSize{ bSize }, revenue{ rvn } {cout <<"I'm 被委派的" <<endl; }//被委派的

Sales_data(const string &, const double bSize, const double, const unsigned);//在類別外定義的建構器

Sales_data(istream &);//在類別外定義的建構器

//Sales_data() {}//與Sales_data() = default;應是一樣的,然不能寫在最前面,會遮蔽後面的建構器 50:00

string bookNo;

double revenue{ 0.00 };//總營收-營業額

unsigned soldQ{ 0 };

double bookSize{0.00};

Sales_data& combine(const Sales_data&, const Sales_data&);//成員函式宣告一定要在類別內,定義可在此外

Sales_data& combine(const Sales_data&);//要改變this及其屬性值就不能再在參數列後加上const;因為加上const後this成了指向常值的常值指標,被指向的東西是不能被改變的了

string isbn()const;

double avg_price()const;

};

ostream& print(ostream& , const Sales_data&);

istream& read(istream& , Sales_data& );

Sales_data add(const Sales_data&, const Sales_data&);

#endif // !SALES_DATA_H

.cpp:

#include "Sales_data.h"

#include<string>

#include<iostream>

using namespace std;



Sales_data::Sales_data(const string& bNo,const double bSize,const double price, const unsigned sdQ) :Sales_data("",0.0,0.0){

cout << "I'm the four arguments one!" << endl;

bookNo = bNo; soldQ = sdQ; revenue = price * sdQ; bookSize = bSize;

}

Sales_data::Sales_data(istream& is):Sales_data("",0.0,0.0)

{

cout<<"I'm the istream one!"<<endl;

read(is, *this);

}

Sales_data& Sales_data::combine(const Sales_data& sales_data1, const Sales_data& sales_data2)

{

this->bookNo = sales_data1.bookNo;

this->bookSize = sales_data1.bookSize;

this->soldQ = sales_data1.soldQ + sales_data2.soldQ;

this->revenue = sales_data1.revenue + sales_data2.revenue;

return *this; // TODO: insert return statement here

}

Sales_data& Sales_data::combine(const Sales_data& sales_data)

{

if (bookNo == "") bookNo = sales_data.bookNo;//因為這是Sales_data類別的成員函式,所以可以直接調用該類別內的所有成員名稱,不必全名稱

bookSize = sales_data.bookSize;

soldQ += sales_data.soldQ;

revenue += sales_data.revenue;

return *this;// TODO: insert return statement here

}

string Sales_data::isbn()const

{

return bookNo;

}



double Sales_data::avg_price()const

{

if (soldQ>0)

{

return revenue / soldQ;

}

return 0.0;

}

ostream& print(ostream& os, const Sales_data& sales_data) {

os << sales_data.isbn() << '\t' << sales_data.soldQ << '\t'

<< sales_data.revenue << '\t' << sales_data.avg_price();

return os;

}



istream& read(istream& is, Sales_data& sales_data) {//要改變引數值,參數一定要是參考,將引數用傳址(參考)方式傳遞

//decltype(cin)& read(istream is ,Sales_data sales_data) {

is >> sales_data.bookNo >> sales_data.soldQ >> sales_data.revenue;

return is;

}



Sales_data add(const Sales_data& sales_data1, const Sales_data& sales_data2)

{

Sales_data sum = sales_data1;

sum.combine(sales_data2);

return sum;

}



練習7.42

1:11:50

練習7.40

Write the class definition for that abstraction.

為了那一類的抽象之事物,寫其抽象的類別定義。

這裡的「abstraction」一樣不宜翻成「抽象層」,翻成「抽象事物」才穩

這兩題都先壓著

1:20:20

頁293

7.5.3. The Role of the Default Constructor

只要是一個物件經過預設或值的初始化時,預設建構器就會自動被套用:

The default constructor is used automatically whenever an object is default or value initialized.



1:40:00

element initializer 元素初始器



Classes must have a default constructor in order to be used in these contexts.

當一個類別中的成員,其型別是沒有預設建構器時,我們若不提供該成員適當的初始器,則會發生錯誤



Best Practices:

In practice, it is almost always right to provide a default constructor if other constructors are being defined.

1:53:00

Using the Default Constructor



頁294

2:3:00

2:8:50

練習7.43

#include <iostream>

using namespace std;

class NoDefault {

int iMem;

public:

NoDefault(int i) :iMem(i){ }

inline int iMemR() { return iMem; }

};



class c

{

public:

c(NoDefault);

~c();//解構器

inline int ndVal() { return nd.iMemR(); }

private:

NoDefault nd;

};



c::c(NoDefault d = NoDefault(0)) :nd(d)

{

}



c::~c()

{

nd = NULL;

}

int main() {

c cTest;

cout<< cTest.ndVal() <<endl;//should be 0

}



2:34:30

練習7.44

vector<NoDefault> vec(10);//Error C2512 'NoDefault::NoDefault': no appropriate default constructor available

/*When we explicitly request value initialization by writing an expressions of the

form T() where T is the name of a type (The vector constructor that takes a

single argument to specify the vector’s size (§ 3.3.1, p. 98) uses an argument

of this kind to value initialize its element initializer.)頁293*/

2:39:44

練習7.45

That’s OK. Because c has a default constructor

int main() {

vector<c> vec(10);

c cTest;

cout<< cTest.ndVal() <<endl;//should be 0

for(c i:vec)

cout << i.ndVal() << endl;//should be 0 of 10 rows

}



練習7.46

複習7.1.4. Constructors

(b)A default constructor is a constructor with an empty parameter list.

類別控制預設初始化的方法是定義一個特殊的建構器,叫做預設建構器(default constructor)。這個預設建構器不接受任何引數。(頁263)

Classes control default initialization by defining a special constructor, known as the default constructor. The default constructor is one that takes no arguments.

(d) If a class does not define a default constructor, the compiler generates one that initializes each data member to the default value of its associated type.

定義預設建構器的第二個理由是,對於某些類別來說,合成的預設建構器所做的事情是錯的。還記得我們說過,定義在一個區塊內的內建或複合型別(例如陣列或指標)物件在預設初始化的時候會有未定義的值(§2.2.1)。同樣的規則也適用於預設初始化的內建型別成員。因此,具有內建或複合型別成員的類別一般都應該在類別內初始化那些成員,或定義它們自己版本的預設建構器。否則,使用者可能會創造出其成員帶有未定義值的物件。(頁263)

WARNING: 具有內建或複合型別成員的類別應該只在所有的這些成員都有類別內初始器的情況下才仰賴合成的預設建構器。(頁263,原文闕「才」字)

某些類別必須定義它們自己的預設建構器的第三個理由是,有時候編譯器無法合成。舉例來說,如果類別有一個成員具有類別型別(class type),而那個類別沒有預設建構器,那麼編譯器就無法初始化該成員。對於這種類別,我們必須定義自己的預設建構器。否則,這種類別將不會有可用的預設建構器。我們會在§13.1.6(頁508)中看到會使得編譯器無法產生適當預設建構器的其他情況。(頁264)

Only fairly simple classes—such as the current definition of Sales_data—can rely on the synthesized default constructor. The most common reason that a class must define its own default constructor is that the compiler generates the default for us only if we do not define any other constructors for the class. If we define any constructors, the class will not have a default constructor unless we define that constructor ourselves.

The basis for this rule is that if a class requires control to initialize an object in one case, then the class is likely to require control in all cases.

A second reason to define the default constructor is that for some classes, the synthesized default constructor does the wrong thing. Remember that objects of builtin or compound type (such as arrays and pointers) that are defined inside a block have undefined value when they are default initialized (§ 2.2.1, p. 43). The same rule applies to members of built-in type that are default initialized. Therefore, classes that have members of built-in or compound type should ordinarily either initialize those members inside the class or define their own version of the default constructor.Otherwise, users could create objects with members that have undefined value.(頁263)

A third reason that some classes must define their own default constructor is that sometimes the compiler is unable to synthesize (這裡翻成「湊出」最好、最宜)one. For example, if a class has a member that has a class type, and that class doesn’t have a default constructor, then the compiler can’t initialize that member. For such classes, we must define our own version of the default constructor. Otherwise, the class will not have a usable default constructor. We’ll see in § 13.1.6 (p. 508) additional circumstances that prevent the

compiler from generating an appropriate default constructor.(頁264)



(c) If there are no meaningful default values for a class, the class should not

provide a default constructor.(頁294)

(c)如果一個類別沒有有意義的預設值,類別就不應該提供預設建構器。

不管類別有沒有一個有意義的預設值,都應該要提供至少一個建構器和一個預設建構器。

所以其實在設計(寫作)一個類別時,就應該至少要寫出二個建構器才對,其中一個,必須是可以不帶引數來調用的預設建構器。

只有(A)是完全沒有爭議、疑慮的:

(a) A class must provide at least one constructor.

一個類別至少必須提供一個建構器。

3:54:10

真正是溫故知新了!

7.5.4. Implicit Class-Type Conversions

隱含的類別型別轉型

自動轉型

默認轉型

預設轉型



目前所學所知的type有四大類:

built-in type 內建型別

compound type 複合型別

library type 程式庫型別

class type 類別型別



類別型別的隱含轉型——用單一引數調用的建構器定義了這樣的轉型:

We also noted that classes can define implicit conversions as well. Every constructor that can be called with a single argument defines an implicit conversion to a class type.

能以單一個引數呼叫的每個建構器都定義了一種轉向(to)某個類別型別的隱含轉換

Such constructors are sometimes referred to as converting constructors .

轉換建構器(converting constructors)



頁295

Note A constructor that can be called with a single argument defines an implicit conversion from the constructor’s parameter type to the class type.

從建構器的參數型別,轉成其類別型別

也就是編譯器會默認地把符合單一參數(引數)的建構器其引數的型別的物件當作這個建構器的引數來傳遞,因之就建構了該類別型別的物件。如:

item.combine(null_book);

明明()中的要Sales_data類別型別,可是因為傳了一個與其建構器的引數數量及型別相符的引數(這裡null_book是string型別,與Sales_data其中一個建構器能「匹配」match),所以編譯器就會自動轉譯為用該建構器來建構一個Sales_data類別型別的物件:

Here we call the Sales_data combine member function with a string argument. This call is perfectly legal; the compiler automatically creates a Sales_data object from the given string . That newly generated (temporary) Sales_data is passed to combine .

4:24:30

Only One Class-Type Conversion Is Allowed

隱含轉型(implicit)一次只能一個,而明確轉型(explicit)就不限了

只允許一個類別型別的轉換

字串字面值(string literal)或character string(字元字串)並非string型別!

因此,這樣是可以的:

string null_book = "9-999-99999-9";(這裡其實就已經隱含轉型一次了)

// constructs a temporary Sales_data object

// with units_sold and revenue equal to 0 and bookNo equal to null_book

item.combine(null_book);

這樣就不行:

// error: requires two user-defined conversions:

// (1) convert "9-999-99999-9" to string

// (2) convert that (temporary) string to Sales_data

item.combine("9-999-99999-9");

If we wanted to make this call, we can do so by explicitly converting the character string to either a string or a Sales_data object:

// ok: explicit conversion to string, implicit conversion to Sales_data

item.combine(string("9-999-99999-9"));

// ok: implicit conversion to string, explicit conversion to Sales_data

item.combine(Sales_data("9-999-99999-9"));

可見字元字串或字串字面值與string有隱含轉型的關係

其實「string("9-999-99999-9")」「Sales_data("9-999-99999-9")」都是單一引數建構器的概念

單一引數自然就是單一型別。

4:30:55

Class-Type Conversions Are Not Always Useful

類別型別的轉型並不總是管用

類別型別轉換並不一定總是有用

4:35:00

頁296

4:40:00 暫存物件等於是disposable 一次性的物件

Effectively, we have constructed an object that is discarded after we add its value into item.

4:42:10

Suppressing Implicit Conversions Defined by Constructors

抑制建構器所定義的隱含轉換

讓建構器定義的隱含轉型失效(disable)不可用。



declaring the constructor as explicit :

將建構器宣告為「explicit」

explicit Sales_data(const std::string &s): bookNo(s) { }

explicit Sales_data(std::istream&);

對單一參數的建構器才需如此:

The explicit keyword is meaningful only on constructors that can be called with a single argument.

Now這樣一來, neither constructor can be used to implicitly create a Sales_data object.

可見其實是隱含地藉由符合引數型別的變數來建構了一個Sales_data的物件,並不是將該變數由其型別轉型為Sales_data類別型別

其實是建構,而不是轉型!

對需要多引數的建構器而言,它本身隱含地就是explicit,就不需要指明designate它是explicit

The explicit keyword is used only on the constructor declaration inside the class. It is not repeated on a definition made outside the class body:

explicit只冠在建構器的宣告中,不會在類別外的建構器定義中

// error: explicit allowed only on a constructor declaration in a class header

explicit Sales_data::Sales_data(istream& is)

{

read(is, *this);

}

4:57:20

explicit Constructors Can Be Used Only for Direct Initialization

explicit建構器只能用於直接初始化

5:5:00

還有一種情況會發生隱含的類別型別的轉型,就是在做拷貝形式的初始化時:

One context in which implicit conversions happen is when we use the copy form of initialization (with an = ) (§ 3.2.1 , p. 84 ). We cannot use an explicit constructor with this form of initialization; we must use direct initialization:

可見=和()的初始化還是有所不同的:

Sales_data item1 (null_book); // ok: direct initialization

// error: cannot use the copy form of initialization with an explicit constructor

Sales_data item2 = null_book;

()才是直接初始化,而=是拷貝初始化

使用=初始化一個變數時,我們是在請求編譯器將右手邊的初始器拷貝到建立出來的物件,藉此copy initialize (拷貝初始化)那個物件。否則的話,如果我們省略那個=,我們用的就是direct initialization (直接初始化)。(頁84)

可見所謂的「直接」就是不用「=」的意思!

可見拷貝初始化也就是間接初始化。

頁297

Explicitly Using Constructors for Conversions

為轉換明確地使用建構器

Although the compiler will not use an explicit constructor for an implicit conversion, we can use such constructors explicitly to force a conversion:

所以並不是宣告為explicit的建構器,就不能執行型別轉換了

明確地使用該建構器也可以說是「具名」地使用:

// ok: the argument is an explicitly constructed Sales_data object

item.combine(Sales_data(null_book));

// ok: static_cast can use an explicit constructor

item.combine(static_cast<Sales_data>(cin));

5:31:30

5:34:10

Library Classes with explicit Constructors

具有explicit建構器的程式庫類別

5:41:10

練習7.47

5:45:20

練習7.48

string null_isbn("9-999-99999-9");//這是直接初始化,和string null_book = "9-999-99999-9"; 拷貝初始化有同等的效力

Sales_data item1(null_isbn);//ok,用單一string 型別的引數的建構器來初始化 item1

Sales_data item2("9-999-99999-9");//字元字串(字串字面值)將會隱含轉成string

在定義上看來有沒有宣告explicit並沒有什麼不同,因為這裡建構器的引數型別是string,並不是Sales_data 類別型別

6:6:50

練習7.49

//(a)Sales_data& combine(Sales_data);

//(b)Sales_data& combine(Sales_data&);//只有這個不行!

//(c)Sales_data& combine(const Sales_data&) const;//要拿掉尾巴的const才行

6:27:10

練習7.50

只有單一參數的建構器需要用上explicit

explicit Person(istream&);

6:47:20

練習7.51

Why do you think vector defines its single-argument constructor as explicit, but string does not?

你認為為什麼……

中文版翻錯成「為什麼你會認為……」

如果把size_type轉型成vector會成什麼樣子!

6:54:59

頁298

7.5.5. Aggregate Classes

7.5.5彙總類別

留言

熱門文章