ฉันมีบางส่วนที่จะใช้รายการเริ่มต้นสมาชิกกับตัวสร้างของฉัน ... แต่ฉันนานลืมเหตุผลที่อยู่เบื้องหลังนี้ ...
คุณใช้รายการเริ่มต้นสมาชิกในตัวสร้างของคุณหรือไม่? ถ้าเป็นเช่นนั้นทำไม ถ้าไม่ทำไมล่ะ
ฉันมีบางส่วนที่จะใช้รายการเริ่มต้นสมาชิกกับตัวสร้างของฉัน ... แต่ฉันนานลืมเหตุผลที่อยู่เบื้องหลังนี้ ...
คุณใช้รายการเริ่มต้นสมาชิกในตัวสร้างของคุณหรือไม่? ถ้าเป็นเช่นนั้นทำไม ถ้าไม่ทำไมล่ะ
คำตอบ:
สำหรับสมาชิกคลาสPODมันไม่ได้สร้างความแตกต่าง แต่เป็นเรื่องของสไตล์ สำหรับสมาชิกคลาสซึ่งเป็นคลาสจากนั้นจะหลีกเลี่ยงการเรียกที่ไม่จำเป็นไปยังตัวสร้างเริ่มต้น พิจารณา:
class A
{
public:
A() { x = 0; }
A(int x_) { x = x_; }
int x;
};
class B
{
public:
B()
{
a.x = 3;
}
private:
A a;
};
ในกรณีนี้ตัวสร้างสำหรับB
จะเรียกตัวสร้างเริ่มต้นสำหรับA
แล้วเริ่มต้นa.x
ที่ 3 วิธีที่ดีกว่าสำหรับตัวB
สร้างของการเรียกตัวสร้างโดยตรงA
ในรายการเริ่มต้น:
B()
: a(3)
{
}
นี้เท่านั้นที่จะเรียกA
's A(int)
คอนสตรัคและไม่ได้สร้างการเริ่มต้นของ ในตัวอย่างนี้ความแตกต่างนั้นเล็กน้อย แต่ลองคิดดูว่าคุณจะA
เป็นผู้สร้างเริ่มต้นที่ทำได้มากกว่าเช่นการจัดสรรหน่วยความจำหรือเปิดไฟล์ คุณไม่ต้องการที่จะทำเช่นนั้นโดยไม่จำเป็น
นอกจากนี้หากคลาสไม่มีคอนสตรัคเตอร์เริ่มต้นหรือคุณมีconst
ตัวแปรสมาชิกคุณต้องใช้รายการ initializer:
class A
{
public:
A(int x_) { x = x_; }
int x;
};
class B
{
public:
B() : a(3), y(2) // 'a' and 'y' MUST be initialized in an initializer list;
{ // it is an error not to do so
}
private:
A a;
const int y;
};
นอกเหนือจากเหตุผลด้านประสิทธิภาพที่กล่าวถึงข้างต้นหากคลาสของคุณเก็บการอ้างอิงถึงวัตถุที่ส่งผ่านเป็นพารามิเตอร์ตัวสร้างหรือคลาสของคุณมีตัวแปร const คุณจะไม่มีตัวเลือกใด ๆ ยกเว้นการใช้รายการเริ่มต้น
เหตุผลหนึ่งที่สำคัญสำหรับการใช้รายการตัวสร้างเริ่มต้นซึ่งไม่ได้กล่าวถึงในคำตอบที่นี่คือการเริ่มต้นของชั้นฐาน
ตามคำสั่งของการก่อสร้างชั้นฐานควรสร้างก่อนชั้นเด็ก หากไม่มีรายการ initializer Constructor นี้เป็นไปได้ถ้าคลาสพื้นฐานของคุณมี Constructor เริ่มต้นซึ่งจะถูกเรียกก่อนที่จะเข้าสู่ Constructor ของคลาสลูก
แต่ถ้าคลาสพื้นฐานของคุณมีคอนสตรัคเตอร์แบบกำหนดพารามิเตอร์เท่านั้นคุณต้องใช้ลิสต์ initializer ของคอนสตรัคเตอร์เพื่อให้แน่ใจว่าคลาสพื้นฐานของคุณเริ่มต้นได้ก่อนคลาสย่อย
การเริ่มต้นของ Subobjects ซึ่งมีคอนสตรัคเตอร์ที่กำหนดพารามิเตอร์เท่านั้น
อย่างมีประสิทธิภาพ
ใช้รายการตัวสร้างเริ่มต้นคุณเริ่มสมาชิกข้อมูลของคุณไปยังสถานะที่แน่นอนที่คุณต้องการในรหัสของคุณแทนที่จะเริ่มต้นพวกเขาเป็นรัฐเริ่มต้นแล้วเปลี่ยนสถานะของพวกเขาเป็นคนที่คุณต้องการในรหัสของคุณ
หากสมาชิกข้อมูล const ที่ไม่คงที่ในชั้นเรียนของคุณมีตัวสร้างเริ่มต้นและคุณไม่ได้ใช้รายการตัวเริ่มต้นของตัวสร้างคุณจะไม่สามารถเริ่มต้นให้เป็นสถานะที่ต้องการได้
สมาชิกข้อมูลอ้างอิงจะต้องเป็น intialized เมื่อคอมไพเลอร์เข้าสู่ Constructor เนื่องจากการอ้างอิงไม่สามารถประกาศและเริ่มต้นได้ในภายหลัง นี่เป็นไปได้เฉพาะกับรายการตัวสร้างเริ่มต้น
ถัดจากปัญหาด้านประสิทธิภาพมีอีกสิ่งหนึ่งที่สำคัญมากซึ่งฉันต้องการเรียกรหัสการบำรุงรักษาและการขยาย
ถ้า T เป็น POD และคุณเริ่มที่จะเลือกรายการการเริ่มต้นจากนั้นถ้าหนึ่งครั้ง T จะเปลี่ยนเป็นชนิดที่ไม่ใช่ POD คุณไม่จำเป็นต้องเปลี่ยนแปลงอะไรเลยเกี่ยวกับการเริ่มต้นเพื่อหลีกเลี่ยงการเรียกคอนสตรัคเตอร์ที่ไม่จำเป็นเพราะมันเหมาะแล้ว
ถ้า type T มี Constructor เริ่มต้นและ Constructor ที่ผู้ใช้กำหนดตั้งแต่หนึ่งตัวขึ้นไปและหนึ่งครั้งที่คุณตัดสินใจที่จะลบหรือซ่อนค่าเริ่มต้นจากนั้นถ้าใช้รายการเตรียมใช้งานคุณไม่จำเป็นต้องอัปเดตรหัสหาก Constructor ที่ผู้ใช้กำหนด มีการใช้งานอย่างถูกต้องแล้ว
เช่นเดียวกับสมาชิก const หรือสมาชิกอ้างอิงสมมติว่าเริ่มแรก T ถูกกำหนดดังนี้:
struct T
{
T() { a = 5; }
private:
int a;
};
ถัดไปคุณตัดสินใจที่จะมีคุณสมบัติเป็น const ถ้าคุณจะใช้รายการเริ่มต้นจากจุดเริ่มต้นนี่คือการเปลี่ยนแปลงบรรทัดเดียว แต่มีการกำหนด T ข้างต้นก็ต้องขุดนิยามตัวสร้างเพื่อลบการกำหนด:
struct T
{
T() : a(5) {} // 2. that requires changes here too
private:
const int a; // 1. one line change
};
ไม่ใช่ความลับที่การบำรุงรักษานั้นง่ายกว่าและมีข้อผิดพลาดน้อยกว่าถ้ารหัสนั้นไม่ได้เขียนโดย "รหัสลิง" แต่โดยวิศวกรผู้ตัดสินใจตามการพิจารณาอย่างลึกซึ้งเกี่ยวกับสิ่งที่เขาทำ
ก่อนที่จะเรียกใช้เนื้อความของตัวสร้างคอนสตรัคเตอร์ทั้งหมดสำหรับคลาสพาเรนต์และจากนั้นสำหรับฟิลด์จะถูกเรียกใช้ โดยค่าเริ่มต้นตัวสร้างไม่มีการโต้แย้งจะถูกเรียก รายการการเตรียมใช้งานช่วยให้คุณสามารถเลือกคอนสตรัคเตอร์ที่จะเรียกและคอนสตรัคเตอร์ที่ได้รับ
หากคุณมีการอ้างอิงหรือเขตข้อมูล const หรือหากหนึ่งในคลาสที่ใช้ไม่มีคอนสตรัคเตอร์เริ่มต้นคุณต้องใช้รายการเริ่มต้น
// Without Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
variable = a;
}
};
คอมไพเลอร์ที่นี่ทำตามขั้นตอนต่อไปนี้เพื่อสร้างวัตถุประเภท MyClass
1 คอนสตรัคเตอร์ประเภทนี้เรียกว่า "a" เป็นอันดับแรก
2. ผู้ดำเนินการที่ได้รับมอบหมายของ“ ประเภท” เรียกว่าภายในร่างกายของ MyClass () คอนสตรัคที่จะกำหนด
variable = a;
จากนั้นในที่สุด destructor ของ“ Type” จะถูกเรียกว่า“ a” เนื่องจากมันอยู่นอกขอบเขต
ตอนนี้ให้พิจารณาโค้ดเดียวกันกับตัวสร้าง MyClass () พร้อมกับ Initializer List
// With Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a):variable(a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
}
};
ด้วยรายการเริ่มต้นทำตามขั้นตอนต่อไปนี้ตามด้วยคอมไพเลอร์:
เพียงแค่เพิ่มข้อมูลเพิ่มเติมเพื่อแสดงให้เห็นว่ารายการการเริ่มต้นสมาชิกแตกต่างกันมากเพียงใด ใน leetcode 303 Range Sum Query - ไม่เปลี่ยนรูปhttps://leetcode.com/problems/range-sum-query-immutable/ซึ่งคุณต้องสร้างและเริ่มต้นเป็นศูนย์เวกเตอร์ที่มีขนาดที่แน่นอน นี่คือการใช้งานและการเปรียบเทียบความเร็วสองแบบที่แตกต่างกัน
โดยไม่ต้องเริ่มต้นสมาชิกรายการที่จะได้รับ AC ค่าใช้จ่ายฉันเกี่ยวกับ212 มิลลิวินาที
class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) {
preSum = vector<int>(nums.size()+1, 0);
int ps = 0;
for (int i = 0; i < nums.size(); i++)
{
ps += nums[i];
preSum[i+1] = ps;
}
}
int sumRange(int i, int j) {
return preSum[j+1] - preSum[i];
}
};
ตอนนี้ใช้รายการเริ่มต้นสมาชิกเวลาที่จะได้รับ AC เป็นเรื่องเกี่ยวกับ108 มิลลิวินาที ด้วยตัวอย่างง่ายๆนี้ก็ค่อนข้างชัดเจนว่ารายการเริ่มต้นสมาชิกเป็นวิธีที่มีประสิทธิภาพมากขึ้น การวัดทั้งหมดมาจากเวลาทำงานจาก LC
class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) : preSum(nums.size()+1, 0) {
int ps = 0;
for (int i = 0; i < nums.size(); i++)
{
ps += nums[i];
preSum[i+1] = ps;
}
}
int sumRange(int i, int j) {
return preSum[j+1] - preSum[i];
}
};
ไวยากรณ์:
class Sample
{
public:
int Sam_x;
int Sam_y;
Sample(): Sam_x(1), Sam_y(2) /* Classname: Initialization List */
{
// Constructor body
}
};
ต้องการเริ่มต้นรายการ:
class Sample
{
public:
int Sam_x;
int Sam_y;
Sample() */* Object and variables are created - i.e.:declaration of variables */*
{ // Constructor body starts
Sam_x = 1; */* Defining a value to the variable */*
Sam_y = 2;
} // Constructor body ends
};
ในโปรแกรมด้านบนเมื่อตัวสร้างคลาสดำเนินการSam_xและSam_yจะถูกสร้างขึ้น จากนั้นในคอนสตรัคเตอร์ตัวแปรข้อมูลสมาชิกจะถูกกำหนด
ใช้กรณี:
ใน C ต้องกำหนดตัวแปรในระหว่างการสร้าง เช่นเดียวกับใน C ++ เราจะต้องเริ่มต้นตัวแปร Const และ Reference ระหว่างการสร้างวัตถุ ถ้าเราเริ่มต้นหลังจากการสร้างวัตถุ (ภายในตัวสร้าง) เราจะได้รับข้อผิดพลาดในเวลารวบรวม
สมาชิกวัตถุของคลาส Sample1 (ฐาน) ซึ่งไม่มีตัวสร้างเริ่มต้น
class Sample1
{
int i;
public:
Sample1 (int temp)
{
i = temp;
}
};
// Class Sample2 contains object of Sample1
class Sample2
{
Sample1 a;
public:
Sample2 (int x): a(x) /* Initializer list must be used */
{
}
};
ในขณะที่การสร้างวัตถุสำหรับคลาสที่ได้รับซึ่งจะเรียกภายในตัวสร้างคลาสที่ได้รับและการเรียกคลาสคอนสตรัคพื้นฐาน (เริ่มต้น) ถ้าคลาสพื้นฐานไม่มีตัวสร้างเริ่มต้นผู้ใช้จะได้รับข้อผิดพลาดเวลารวบรวม เพื่อหลีกเลี่ยงเราจะต้องมีทั้ง
1. Default constructor of Sample1 class
2. Initialization list in Sample2 class which will call the parametric constructor of Sample1 class (as per above program)
ชื่อพารามิเตอร์ของตัวสร้างคลาสและสมาชิกข้อมูลของคลาสเดียวกัน:
class Sample3 {
int i; /* Member variable name : i */
public:
Sample3 (int i) /* Local variable name : i */
{
i = i;
print(i); /* Local variable: Prints the correct value which we passed in constructor */
}
int getI() const
{
print(i); /*global variable: Garbage value is assigned to i. the expected value should be which we passed in constructor*/
return i;
}
};
อย่างที่เราทุกคนรู้กันว่าตัวแปรท้องถิ่นที่มีความสำคัญสูงสุดและตัวแปรทั่วโลกหากตัวแปรทั้งสองมีชื่อเหมือนกัน ในกรณีนี้โปรแกรมพิจารณาค่า "i" {ตัวแปรด้านซ้ายและด้านขวา เช่น: i = i} เป็นตัวแปรโลคัลใน Sample3 () Constructor และ Class Member variable (i) มีการแทนที่ เพื่อหลีกเลี่ยงเราจะต้องใช้อย่างใดอย่างหนึ่ง
1. Initialization list
2. this operator.
ตามที่อธิบายไว้ใน C ++ Core Guidelines C.49: การกำหนดค่าเริ่มต้นให้เหมาะกับการกำหนดใน Constructor นั้นจะป้องกันการเรียกที่ไม่จำเป็นไปยัง Constructor เริ่มต้น