ฉันเชื่อว่าคุณมีการสังเกตที่ถูกต้อง แต่การตีความผิด!
การคัดลอกจะไม่เกิดขึ้นโดยส่งคืนค่าเนื่องจากคอมไพเลอร์ฉลาดทั่วไปทุกรายจะใช้(N) RVOในกรณีนี้ ตั้งแต่ C ++ 17 นี่เป็นข้อบังคับดังนั้นคุณจะไม่เห็นสำเนาใด ๆ โดยส่งคืนเวกเตอร์ที่สร้างขึ้นในเครื่องจากฟังก์ชัน
ตกลงให้เล่นด้วยstd::vector
และสิ่งที่จะเกิดขึ้นในระหว่างการก่อสร้างหรือโดยการกรอกทีละขั้นตอน
ก่อนอื่นให้สร้างประเภทข้อมูลที่ทำให้ทุกสำเนาหรือย้ายปรากฏเช่นนี้
template <typename DATA >
struct VisibleCopy
{
private:
DATA data;
public:
VisibleCopy( const DATA& data_ ): data{ data_ }
{
std::cout << "Construct " << data << std::endl;
}
VisibleCopy( const VisibleCopy& other ): data{ other.data }
{
std::cout << "Copy " << data << std::endl;
}
VisibleCopy( VisibleCopy&& other ) noexcept : data{ std::move(other.data) }
{
std::cout << "Move " << data << std::endl;
}
VisibleCopy& operator=( const VisibleCopy& other )
{
data = other.data;
std::cout << "copy assign " << data << std::endl;
}
VisibleCopy& operator=( VisibleCopy&& other ) noexcept
{
data = std::move( other.data );
std::cout << "move assign " << data << std::endl;
}
DATA Get() const { return data; }
};
และตอนนี้เรามาเริ่มการทดลองกัน:
using T = std::vector< VisibleCopy<int> >;
T Get1()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec{ 1,2,3,4 };
std::cout << "End init" << std::endl;
return vec;
}
T Get2()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec(4,0);
std::cout << "End init" << std::endl;
return vec;
}
T Get3()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
T Get4()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.reserve(4);
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
int main()
{
auto vec1 = Get1();
auto vec2 = Get2();
auto vec3 = Get3();
auto vec4 = Get4();
// All data as expected? Lets check:
for ( auto& el: vec1 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec2 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec3 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec4 ) { std::cout << el.Get() << std::endl; }
}
สิ่งที่เราสังเกตได้:
ตัวอย่างที่ 1) เราสร้างเวกเตอร์จากรายการ initializer และบางทีเราคาดว่าเราจะเห็นการสร้าง 4 ครั้งและการเคลื่อนไหว 4 ครั้ง แต่เราได้รับ 4 ชุด! ฟังดูลึกลับไปหน่อย แต่เหตุผลก็คือการใช้งาน initializer list! เพียงไม่อนุญาตให้ย้ายจากรายการเนื่องจากตัววนซ้ำจากรายการเป็นสิ่งconst T*
ที่ทำให้ไม่สามารถย้ายองค์ประกอบออกจากรายการได้ คำตอบโดยละเอียดเกี่ยวกับหัวข้อนี้อยู่ที่นี่: initializer_list และย้ายซีแมนทิกส์
ตัวอย่างที่ 2) ในกรณีนี้เราจะได้รับการสร้างเริ่มต้นและมีค่า 4 ชุด นั่นคือไม่มีอะไรพิเศษและเป็นสิ่งที่เราคาดหวัง
ตัวอย่างที่ 3) นอกจากนี้ที่นี่เรามีการก่อสร้างและการเคลื่อนไหวบางอย่างตามที่คาดไว้ ด้วยการใช้ stl ของฉันเวกเตอร์จะโตขึ้นตามตัวคูณ 2 ทุกครั้ง ดังนั้นเราเห็นโครงสร้างแรกอีกอันหนึ่งและเนื่องจากเวกเตอร์ปรับขนาดจาก 1 เป็น 2 เราจึงเห็นการย้ายองค์ประกอบแรก ในขณะที่เพิ่ม 3 เราจะเห็นการปรับขนาดจาก 2 เป็น 4 ซึ่งต้องการการย้ายองค์ประกอบสองรายการแรก ทั้งหมดตามที่คาดไว้!
ตัวอย่างที่ 4) ตอนนี้เราจองพื้นที่และเติมในภายหลัง ตอนนี้เราไม่มีการคัดลอกและไม่มีการเคลื่อนไหวอีกต่อไป!
ในทุกกรณีเราไม่เห็นการย้ายหรือการคัดลอกโดยการคืนเวกเตอร์กลับไปยังผู้โทรเลย! (N) RVO กำลังเกิดขึ้นและไม่จำเป็นต้องดำเนินการใด ๆ เพิ่มเติมในขั้นตอนนี้!
กลับไปที่คำถามของคุณ:
"วิธีค้นหาการคัดลอกปลอมแบบ C ++"
ดังที่เห็นด้านบนคุณอาจแนะนำคลาสพร็อกซีในระหว่างนั้นเพื่อการดีบัก
การทำให้สำเนา -ctor เป็นส่วนตัวอาจไม่ทำงานในหลายกรณีเนื่องจากคุณอาจมีสำเนาที่ต้องการและมีบางส่วนที่ซ่อนอยู่ ข้างต้นเฉพาะโค้ดสำหรับ 4 ตัวอย่างเท่านั้นที่จะทำงานร่วมกับ ctor แบบส่วนตัว! และฉันไม่สามารถตอบคำถามได้หากตัวอย่างที่ 4 เป็นวิธีที่เร็วที่สุดในขณะที่เราเติมเต็มความสงบด้วยความสงบสุข
ขออภัยที่ฉันไม่สามารถเสนอทางออกทั่วไปสำหรับการค้นหาสำเนา "ไม่พึงประสงค์" ที่นี่ แม้ว่าคุณจะขุดรหัสของคุณสำหรับการโทรmemcpy
คุณจะไม่พบทั้งหมดตามที่memcpy
จะได้รับการปรับให้เหมาะสมและคุณจะเห็นคำแนะนำแอสเซมเบลอร์แอสเซมเบลอร์ทำงานโดยตรงโดยไม่ต้องเรียกใช้memcpy
ฟังก์ชันห้องสมุดของคุณ
คำแนะนำของฉันไม่ได้มุ่งเน้นไปที่ปัญหาเล็ก ๆ น้อย ๆ หากคุณมีปัญหาด้านประสิทธิภาพการทำงานให้ใช้เครื่องมือสร้างโปรไฟล์และการวัด มีนักฆ่าประสิทธิภาพที่มีศักยภาพมากมายที่ใช้เวลามากกับการmemcpy
ใช้งานแบบปลอมดูเหมือนจะไม่เป็นความคิดที่คุ้มค่า
std::vector
โดยวิธีการใด ๆ ที่ไม่ได้เป็นสิ่งที่มันอ้างว่าจะเป็น ตัวอย่างของคุณแสดงสำเนาที่ชัดเจนและเป็นเรื่องธรรมดาและเป็นวิธีที่ถูกต้อง (อีกครั้งที่ imho) เพื่อใช้std::move
ฟังก์ชั่นตามที่คุณแนะนำตัวเองหากสำเนาไม่ใช่สิ่งที่คุณต้องการ โปรดทราบว่าคอมไพเลอร์บางตัวอาจละเว้นการคัดลอกหากเปิดใช้งานการปรับแต่งค่าสถานะและเวกเตอร์ไม่เปลี่ยนแปลง