การคัดลอกข้อมูลคืออะไร? การเพิ่มประสิทธิภาพค่าที่ส่งคืนคืออะไร พวกเขาหมายถึงอะไร
พวกเขาสามารถเกิดขึ้นได้ในสถานการณ์ใด ข้อ จำกัด คืออะไร
- หากคุณได้รับการอ้างอิงกับคำถามนี้คุณอาจจะมองหาการแนะนำ
- สำหรับภาพรวมทางเทคนิคดูมาตรฐานอ้างอิง
- ดูกรณีทั่วไปที่นี่
การคัดลอกข้อมูลคืออะไร? การเพิ่มประสิทธิภาพค่าที่ส่งคืนคืออะไร พวกเขาหมายถึงอะไร
พวกเขาสามารถเกิดขึ้นได้ในสถานการณ์ใด ข้อ จำกัด คืออะไร
คำตอบ:
สำหรับภาพรวมทางเทคนิค - ข้ามไปที่คำตอบนี้
สำหรับกรณีทั่วไปที่มีการคัดลอกเกิดขึ้น - ข้ามไปที่คำตอบนี้
การคัดลอกข้อมูลเป็นการเพิ่มประสิทธิภาพที่คอมไพเลอร์ส่วนใหญ่นำมาใช้เพื่อป้องกันการทำสำเนาเพิ่มเติม (อาจมีราคาแพง) ในบางสถานการณ์ มันทำให้ผลตอบแทนตามมูลค่าหรือเป็นไปได้ในทางปฏิบัติในทางปฏิบัติ (มีข้อ จำกัด )
มันเป็นเพียงรูปแบบของการเพิ่มประสิทธิภาพที่ elides (ฮ่า!) ตามที่ถ้ากฎ - สำเนาตัดออกสามารถนำไปใช้แม้ว่าการคัดลอก / ย้ายวัตถุที่มีผลข้างเคียง
ตัวอย่างต่อไปนี้นำมาจากWikipedia :
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C();
}
int main() {
std::cout << "Hello World!\n";
C obj = f();
}
ทั้งนี้ขึ้นอยู่กับคอมไพเลอร์และการตั้งค่าเอาท์พุทต่อไปนี้จะถูกต้องทั้งหมด :
สวัสดีชาวโลก!
ทำสำเนาแล้ว
ทำสำเนาแล้ว
สวัสดีชาวโลก!
ทำสำเนาแล้ว
สวัสดีชาวโลก!
สิ่งนี้ยังหมายถึงวัตถุที่สามารถสร้างได้น้อยลงดังนั้นคุณจึงไม่สามารถพึ่งพาจำนวน destructors ที่ระบุได้ คุณไม่ควรมีตรรกะที่สำคัญในการคัดลอก / ย้าย - คอนสตรัคเตอร์หรือ destructors เนื่องจากคุณไม่สามารถพึ่งพาพวกเขาถูกเรียก
หากการเรียกไปที่ตัวคัดลอกหรือย้ายตัวสร้างถูกนำออกไปตัวสร้างนั้นจะต้องยังคงอยู่และต้องสามารถเข้าถึงได้ วิธีนี้ช่วยให้มั่นใจได้ว่าการคัดลอกข้อมูลไม่อนุญาตให้คัดลอกวัตถุที่ไม่สามารถคัดลอกได้ตามปกติเช่นเนื่องจากมีตัวสร้างสำเนา / ย้ายแบบส่วนตัวหรือถูกลบ
C ++ 17 : ตั้งแต่ C ++ 17 รับประกันการคัดลอก Elision เมื่อวัตถุถูกส่งคืนโดยตรง:
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C(); //Definitely performs copy elision
}
C g() {
C c;
return c; //Maybe performs copy elision
}
int main() {
std::cout << "Hello World!\n";
C obj = f(); //Copy constructor isn't called
}
สำหรับมุมมองทางเทคนิค & การแนะนำน้อย - ข้ามไปที่คำตอบนี้
สำหรับกรณีทั่วไปที่มีการคัดลอกเกิดขึ้น - ข้ามไปที่คำตอบนี้
ตัวเลือกคัดลอกถูกกำหนดไว้ในมาตรฐานใน:
เช่น
31) เมื่อตรงตามเกณฑ์ที่กำหนดการอนุญาตให้ละเว้นการสร้างสำเนา / ย้ายของคลาสวัตถุแม้ว่าตัวสร้างสำเนา / ย้ายและ / หรือ destructor สำหรับวัตถุนั้นมีผลข้างเคียง ในกรณีดังกล่าวการใช้งานจะปฏิบัติต่อแหล่งที่มาและเป้าหมายของการดำเนินการคัดลอก / ย้ายที่ถูกละเว้นเป็นเพียงสองวิธีที่ต่างกันในการอ้างถึงวัตถุเดียวกันและการทำลายวัตถุนั้นจะเกิดขึ้นในภายหลังในเวลาที่วัตถุทั้งสอง ทำลายโดยไม่มีการเพิ่มประสิทธิภาพ 123การคัดลอก / ย้ายการดำเนินการนี้เรียกว่าการคัดลอกการคัดลอกได้รับอนุญาตในสถานการณ์ต่อไปนี้ (ซึ่งอาจรวมกันเพื่อกำจัดสำเนาหลายชุด):
- ในคำสั่ง return ในฟังก์ชั่นที่มีประเภทคืนคลาสเมื่อนิพจน์เป็นชื่อของวัตถุอัตโนมัติที่ไม่ลบเลือน (นอกเหนือจากฟังก์ชั่นหรือพารามิเตอร์ catch-clause) ที่มีชนิด cvunqualified เช่นเดียวกับชนิดส่งคืนฟังก์ชัน การดำเนินการคัดลอก / ย้ายสามารถละเว้นได้โดยการสร้างวัตถุอัตโนมัติโดยตรงในค่าตอบแทนของฟังก์ชั่น
- ในการแสดงออกเมื่อตัวถูกดำเนินการเป็นชื่อของวัตถุอัตโนมัติที่ไม่ลบเลือน (นอกเหนือจากฟังก์ชั่นหรือพารามิเตอร์ catch-clause) ที่มีขอบเขตไม่ขยายเกินกว่าจุดสิ้นสุดของการลองบล็อกในสุด (ถ้ามี หนึ่ง) การดำเนินการคัดลอก / ย้ายจากตัวถูกดำเนินการไปยังวัตถุข้อยกเว้น (15.1) สามารถละเว้นได้โดยการสร้างวัตถุอัตโนมัติโดยตรงในวัตถุข้อยกเว้น
- เมื่อวัตถุคลาสชั่วคราวที่ไม่ได้ถูกผูกไว้กับการอ้างอิง (12.2) จะถูกคัดลอก / ย้ายไปยังวัตถุคลาสที่มีชนิด CV-unqualified เดียวกันการดำเนินการคัดลอก / ย้ายสามารถละเว้นได้โดยการสร้างวัตถุชั่วคราวโดยตรงใน เป้าหมายของการคัดลอก / ย้ายที่ถูกละไว้
- เมื่อข้อยกเว้นการประกาศของตัวจัดการข้อยกเว้น (ข้อ 15) ประกาศวัตถุประเภทเดียวกัน (ยกเว้นคุณสมบัติ CVV) เป็นวัตถุยกเว้น (15.1) การดำเนินการคัดลอก / ย้ายสามารถละเว้นได้โดยการปฏิบัติกับข้อยกเว้นประกาศ เป็นนามแฝงสำหรับวัตถุข้อยกเว้นหากความหมายของโปรแกรมจะไม่เปลี่ยนแปลงยกเว้นการดำเนินการของตัวสร้างและ destructors สำหรับวัตถุที่ประกาศโดยการประกาศข้อยกเว้น
123) เนื่องจากมีเพียงวัตถุเดียวเท่านั้นที่ถูกทำลายแทนที่จะเป็นสองวัตถุและตัวสร้างสำเนา / ย้ายไม่ถูกดำเนินการจึงยังมีวัตถุหนึ่งชิ้นที่ถูกทำลายสำหรับแต่ละสิ่งก่อสร้าง
ตัวอย่างที่ให้มาคือ:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
และอธิบาย:
นี่คือเกณฑ์ในการตัดออกสามารถรวมกันเพื่อกำจัดสองสายเพื่อสร้างสำเนาของชั้นเรียน
Thing
: การคัดลอกวัตถุอัตโนมัติท้องถิ่นt
ลงในวัตถุชั่วคราวสำหรับค่าตอบแทนของฟังก์ชั่นและการคัดลอกของวัตถุชั่วคราวที่เป็นวัตถุf()
t2
ได้อย่างมีประสิทธิภาพการก่อสร้างของวัตถุในท้องถิ่นt
สามารถดูเป็นการเริ่มต้นวัตถุทั่วโลกโดยตรงt2
และการทำลายของวัตถุนั้นจะเกิดขึ้นที่ทางออกของโปรแกรม การเพิ่มตัวสร้างการย้ายไปยังสิ่งนั้นมีผลเหมือนกัน แต่เป็นการสร้างการย้ายจากวัตถุชั่วคราวไปยังt2
สิ่งที่ถูกลบออก
สำหรับภาพรวมทางเทคนิค - ข้ามไปที่คำตอบนี้
สำหรับมุมมองทางเทคนิค & การแนะนำน้อย - ข้ามไปที่คำตอบนี้
(ชื่อแล้ว) การปรับค่าส่งคืนเป็นรูปแบบทั่วไปของการคัดลอกส่วน มันหมายถึงสถานการณ์ที่วัตถุส่งคืนโดยค่าจากวิธีการมีการคัดลอก elided ตัวอย่างที่กำหนดไว้ในมาตรฐานแสดงให้เห็นถึงการเพิ่มประสิทธิภาพค่าที่ส่งคืนที่มีชื่อเนื่องจากวัตถุมีชื่อ
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
การปรับค่าส่งคืนตามปกติเกิดขึ้นเมื่อมีการส่งคืนชั่วคราว:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
return Thing();
}
Thing t2 = f();
สถานที่ทั่วไปอื่น ๆ ที่เกิดการคัดลอกเกิดขึ้นคือเมื่อมีการส่งค่าชั่วคราวโดย :
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
void foo(Thing t);
foo(Thing());
หรือเมื่อมีการโยนข้อยกเว้นและตามค่า :
struct Thing{
Thing();
Thing(const Thing&);
};
void foo() {
Thing c;
throw c;
}
int main() {
try {
foo();
}
catch(Thing c) {
}
}
ข้อ จำกัด ทั่วไปของการคัดลอกสำเนาคือ:
คอมไพเลอร์เกรดเชิงพาณิชย์ส่วนใหญ่รองรับการคัดลอกตัวเลือก & (N) RVO (ขึ้นอยู่กับการตั้งค่าการปรับให้เหมาะสม)
Copy elision เป็นเทคนิคการปรับแต่งคอมไพเลอร์ที่กำจัดการคัดลอก / ย้ายวัตถุโดยไม่จำเป็น
ในสถานการณ์ต่อไปนี้คอมไพเลอร์ได้รับอนุญาตให้ละเว้นการดำเนินการคัดลอก / ย้ายและด้วยเหตุนี้จะไม่เรียกตัวสร้างที่เกี่ยวข้อง:
#include <iostream>
using namespace std;
class ABC
{
public:
const char *a;
ABC()
{ cout<<"Constructor"<<endl; }
ABC(const char *ptr)
{ cout<<"Constructor"<<endl; }
ABC(ABC &obj)
{ cout<<"copy constructor"<<endl;}
ABC(ABC&& obj)
{ cout<<"Move constructor"<<endl; }
~ABC()
{ cout<<"Destructor"<<endl; }
};
ABC fun123()
{ ABC obj; return obj; }
ABC xyz123()
{ return ABC(); }
int main()
{
ABC abc;
ABC obj1(fun123());//NRVO
ABC obj2(xyz123());//NRVO
ABC xyz = "Stack Overflow";//RVO
return 0;
}
**Output without -fno-elide-constructors**
root@ajay-PC:/home/ajay/c++# ./a.out
Constructor
Constructor
Constructor
Constructor
Destructor
Destructor
Destructor
Destructor
**Output with -fno-elide-constructors**
root@ajay-PC:/home/ajay/c++# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors
root@ajay-PC:/home/ajay/c++# ./a.out
Constructor
Constructor
Move constructor
Destructor
Move constructor
Destructor
Constructor
Move constructor
Destructor
Move constructor
Destructor
Constructor
Move constructor
Destructor
Destructor
Destructor
Destructor
Destructor
แม้เมื่อการคัดลอกเกิดขึ้นและไม่มีการเรียก copy- / move-constructor จะต้องมีอยู่และสามารถเข้าถึงได้ (ราวกับว่าไม่มีการเพิ่มประสิทธิภาพเกิดขึ้นเลย) มิฉะนั้นโปรแกรมจะถูกจัดรูปแบบไม่ถูกต้อง
คุณควรอนุญาตการคัดลอกดังกล่าวเฉพาะในสถานที่ที่จะไม่ส่งผลกระทบต่อพฤติกรรมที่สังเกตเห็นได้ของซอฟต์แวร์ของคุณ การคัดลอกการคัดลอกเป็นรูปแบบเดียวของการเพิ่มประสิทธิภาพที่อนุญาตให้มี (เช่น elide) ผลข้างเคียงที่สังเกตได้ ตัวอย่าง:
#include <iostream>
int n = 0;
class ABC
{ public:
ABC(int) {}
ABC(const ABC& a) { ++n; } // the copy constructor has a visible side effect
}; // it modifies an object with static storage duration
int main()
{
ABC c1(21); // direct-initialization, calls C::C(42)
ABC c2 = ABC(21); // copy-initialization, calls C::C( C(42) )
std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise
return 0;
}
Output without -fno-elide-constructors
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp
root@ajay-PC:/home/ayadav# ./a.out
0
Output with -fno-elide-constructors
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors
root@ajay-PC:/home/ayadav# ./a.out
1
GCC มี-fno-elide-constructors
ตัวเลือกให้ปิดการใช้งานการคัดลอกข้อมูล -fno-elide-constructors
หากคุณต้องการที่จะหลีกเลี่ยงการตัดออกสำเนาที่เป็นไปได้ใช้
ตอนนี้คอมไพเลอร์เกือบทั้งหมดจะมีตัวเลือกการคัดลอกเมื่อเปิดใช้งานการปรับให้เหมาะสมที่สุด
ด้วยการคัดลอกแต่ละครั้งจะมีการข้ามการสร้างหนึ่งรายการและการทำลายการจับคู่หนึ่งสำเนาซึ่งจะช่วยประหยัดเวลาของ CPU และไม่ได้สร้างวัตถุใดวัตถุหนึ่งจึงประหยัดพื้นที่บนเฟรมสแต็ก
ABC obj2(xyz123());
มันคือ NRVO หรือ RVO? มันไม่ได้รับตัวแปร / วัตถุชั่วคราวเช่นเดียวกับ ABC xyz = "Stack Overflow";//RVO