มีความแตกต่างระหว่างการเริ่มต้นการคัดลอกและการเริ่มต้นโดยตรงหรือไม่?


244

สมมติว่าฉันมีฟังก์ชั่นนี้:

void my_test()
{
    A a1 = A_factory_func();
    A a2(A_factory_func());

    double b1 = 0.5;
    double b2(0.5);

    A c1;
    A c2 = A();
    A c3(A());
}

ในการจัดกลุ่มแต่ละข้อความเหล่านี้เหมือนกันหรือไม่ หรือมีสำเนาพิเศษ (อาจปรับให้เหมาะสม) ในการกำหนดค่าเริ่มต้นบางส่วนได้หรือไม่

ฉันเคยเห็นคนพูดทั้งสองอย่าง โปรดอ้างอิงข้อความเป็นหลักฐาน โปรดเพิ่มกรณีอื่นด้วย


1
และมีกรณีที่สี่ที่กล่าวถึงโดย @JohannesSchaub A c1; A c2 = c1; A c3(c1);-
Dan Nissenbaum

1
เพียง 2,018 หมายเหตุ:กฎระเบียบที่มีการเปลี่ยนแปลงในC ++ 17ดูเช่นที่นี่ หากความเข้าใจของฉันถูกต้องใน C ++ 17 งบทั้งสองจะเหมือนกันอย่างมีประสิทธิภาพ (แม้ว่า ctor การคัดลอกจะชัดเจน) นอกจากนี้หากนิพจน์ init เป็นประเภทอื่นที่ไม่ใช่การAเริ่มต้นการคัดลอกจะไม่ต้องการการมีอยู่ของ const / move constuctor นี่คือเหตุผลที่std::atomic<int> a = 1;เป็น ok ใน C ++ 17 แต่ไม่ก่อน
Daniel Langr

คำตอบ:


246

การอัปเดต C ++ 17

ใน C ++ 17 ความหมายของการA_factory_func()เปลี่ยนจากการสร้างวัตถุชั่วคราว (C ++ <= 14) เป็นเพียงการระบุการกำหนดค่าเริ่มต้นของวัตถุใดก็ตามที่นิพจน์นี้ถูกกำหนดค่าเริ่มต้นเป็น (พูดอย่างหลวม ๆ ) ใน C ++ 17 วัตถุเหล่านี้ (เรียกว่า "วัตถุผลลัพธ์") เป็นตัวแปรที่สร้างขึ้นโดยการประกาศ (เหมือนa1) วัตถุประดิษฐ์ที่สร้างขึ้นเมื่อการเริ่มต้นสิ้นสุดลงด้วยการถูกทิ้งหรือหากวัตถุนั้นจำเป็นสำหรับการอ้างอิงอ้างอิง (เช่นในA_factory_func();กรณีสุดท้าย วัตถุถูกสร้างขึ้นเทียมเรียกว่า "การกลายเป็นวัตถุชั่วคราว" เนื่องจากA_factory_func()ไม่มีตัวแปรหรือการอ้างอิงที่จะต้องมีวัตถุอยู่)

เป็นตัวอย่างในกรณีของเราในกรณีของa1และa2กฎพิเศษกล่าวว่าในการประกาศดังกล่าววัตถุผลของการเริ่มต้น prvalue ประเภทเดียวกันเป็นa1ตัวแปรa1และดังนั้นจึงA_factory_func()เริ่มต้นวัตถุa1โดยตรง การทำงานแบบคนกลางจะไม่ส่งผลใด ๆ เพราะA_factory_func(another-prvalue)เพียงแค่ "ส่งผ่าน" วัตถุผลลัพธ์ของค่า prvalue ด้านนอกเพื่อให้เป็นวัตถุผลลัพธ์ของ prvalue ด้านใน


A a1 = A_factory_func();
A a2(A_factory_func());

ขึ้นอยู่กับประเภทA_factory_func()ผลตอบแทน ฉันคิดว่ามันส่งกลับA- แล้วก็ทำแบบเดียวกัน - ยกเว้นว่าเมื่อตัวสร้างการคัดลอกชัดเจนแล้วคนแรกจะล้มเหลว อ่าน8.6 / 14

double b1 = 0.5;
double b2(0.5);

สิ่งนี้ทำเช่นเดียวกันเพราะเป็นชนิดในตัว (ซึ่งหมายความว่าไม่ใช่ประเภทของชั้นเรียนที่นี่) อ่าน8.6 / 14

A c1;
A c2 = A();
A c3(A());

สิ่งนี้ไม่ได้ทำแบบเดียวกัน การเริ่มต้นครั้งแรกเริ่มต้นถ้าAไม่ใช่ POD และไม่ทำการเริ่มต้นใด ๆ สำหรับ POD (อ่าน8.6 / 9 ) สำเนาที่สองเริ่มต้น: ค่าเริ่มต้นชั่วคราวแล้วคัดลอกค่านั้นลงในc2(อ่าน5.2.3 / 2และ8.6 / 14 ) หลักสูตรนี้จะต้องมีตัวสร้างสำเนาที่ไม่ชัดเจน (อ่าน8.6 / 14และ12.3.1 / 3และ13.3.1.3/1 ) ที่สามสร้างการประกาศฟังก์ชั่นสำหรับฟังก์ชั่นc3ที่ส่งกลับAและที่จะใช้ฟังก์ชั่นตัวชี้ไปยังฟังก์ชั่นกลับA(อ่าน8.2 )


การเจาะลึกการกำหนดค่าเริ่มต้นโดยตรงและคัดลอกการเริ่มต้น

ในขณะที่พวกเขาดูเหมือนกันและควรจะทำแบบเดียวกันทั้งสองรูปแบบมีความแตกต่างอย่างน่าทึ่งในบางกรณี การกำหนดค่าเริ่มต้นสองรูปแบบนั้นโดยตรงและคัดลอกการเริ่มต้น:

T t(x);
T t = x;

มีพฤติกรรมที่เราสามารถระบุคุณลักษณะแต่ละรายการได้:

  • พฤติกรรมการเริ่มต้นตรงเช่นการเรียกฟังก์ชั่นการทำงานที่มากเกินไป: ฟังก์ชั่นในกรณีนี้มีการก่อสร้างของT(รวมทั้งexplicitคน) xและอาร์กิวเมนต์เป็น ความละเอียดเกินพิกัดจะค้นหาตัวสร้างที่ตรงกันที่สุดและเมื่อจำเป็นจะทำการแปลงที่จำเป็น
  • คัดลอกการเริ่มต้นสร้างลำดับการแปลงนัย: มันพยายามที่จะแปลงไปยังวัตถุของการพิมพ์x T(จากนั้นอาจคัดลอกวัตถุนั้นลงในวัตถุที่จะเริ่มต้นได้ดังนั้นตัวสร้างสำเนาก็จำเป็นเช่นกัน - แต่นี่ไม่ใช่สิ่งสำคัญด้านล่าง)

ดังที่คุณเห็นการเริ่มต้นการคัดลอกเป็นส่วนหนึ่งของการเริ่มต้นโดยตรงในแง่ของการแปลงโดยนัยที่เป็นไปได้: ในขณะที่การเริ่มต้นโดยตรงมีตัวสร้างทั้งหมดที่สามารถโทรได้และนอกจากนี้ยังสามารถทำการแปลงโดยนัย สามารถตั้งค่าลำดับการแปลงโดยนัยได้หนึ่งชุด

ฉันพยายามอย่างหนักและได้รหัสต่อไปนี้เพื่อส่งข้อความที่แตกต่างกันสำหรับแต่ละรูปแบบเหล่านั้นโดยไม่ต้องใช้ "ชัดเจน" ผ่านตัวexplicitสร้าง

#include <iostream>
struct B;
struct A { 
  operator B();
};

struct B { 
  B() { }
  B(A const&) { std::cout << "<direct> "; }
};

A::operator B() { std::cout << "<copy> "; return B(); }

int main() { 
  A a;
  B b1(a);  // 1)
  B b2 = a; // 2)
}
// output: <direct> <copy>

มันทำงานอย่างไรและทำไมมันถึงออกผลลัพธ์นั้น

  1. การเริ่มต้นโดยตรง

    ก่อนอื่นไม่รู้เกี่ยวกับการแปลง มันจะพยายามเรียกตัวสร้าง ในกรณีนี้ตัวสร้างต่อไปนี้พร้อมใช้งานและเป็นการจับคู่ที่ตรงกัน :

    B(A const&)

    มีการแปลงไม่น้อยกว่าการแปลงที่กำหนดโดยผู้ใช้จำเป็นต้องเรียกคอนสตรัคเตอร์นั้น (โปรดทราบว่าไม่มีการแปลงคุณสมบัติแบบ const ที่นี่) และการเริ่มต้นโดยตรงจะเรียกมันว่า

  2. คัดลอกการเริ่มต้น

    ดังกล่าวข้างต้นการเริ่มต้นคัดลอกจะสร้างลำดับการแปลงเมื่อaไม่ได้พิมพ์Bหรือได้รับมาจากมัน (ซึ่งเป็นกรณีที่ชัดเจนที่นี่) ดังนั้นมันจะมองหาวิธีที่จะทำการแปลงและจะหาผู้สมัครดังต่อไปนี้

    B(A const&)
    operator B(A&);

    สังเกตว่าฉันเขียนฟังก์ชั่นการแปลงใหม่ได้อย่างไร: ประเภทพารามิเตอร์สะท้อนให้เห็นถึงประเภทของthisตัวชี้ซึ่งในฟังก์ชั่นที่ไม่ใช่สมาชิก const จะไม่ใช่แบบ const ตอนนี้เราเรียกผู้สมัครเหล่านี้xว่าเป็นข้อโต้แย้ง ผู้ชนะคือฟังก์ชั่นการแปลง: เพราะถ้าเรามีสองฟังก์ชั่นผู้สมัครทั้งสองยอมรับการอ้างอิงประเภทเดียวกันแล้วรุ่นconst น้อยชนะ (นี่คือโดยวิธีการยังกลไกที่ชอบฟังก์ชั่นที่ไม่ใช่สมาชิก const เรียกร้องให้ไม่ใช่ วัตถุ -const)

    โปรดทราบว่าหากเราเปลี่ยนฟังก์ชั่นการแปลงให้เป็นฟังก์ชั่นสมาชิก const การแปลงนั้นจะคลุมเครือ (เพราะทั้งคู่มีประเภทพารามิเตอร์ที่A const&แล้ว): คอมไพเลอร์ Comeau ปฏิเสธอย่างถูกต้อง แต่ GCC ยอมรับมันในโหมดที่ไม่ใช่ทางตัน -pedanticแม้ว่าการเปลี่ยนไปใช้จะทำให้มันส่งสัญญาณเตือนความกำกวมที่เหมาะสมเช่นกัน

ฉันหวังว่านี่จะช่วยให้ชัดเจนขึ้นว่าแบบฟอร์มทั้งสองแตกต่างกันอย่างไร!


ว้าว. ฉันไม่ได้ตระหนักถึงการประกาศฟังก์ชั่น ฉันต้องยอมรับคำตอบของคุณเพียงเพราะเป็นคนเดียวที่รู้เรื่องนั้น มีเหตุผลที่ฟังก์ชั่นการประกาศทำงานอย่างนั้นหรือไม่? มันจะดีกว่าถ้า c3 ได้รับการปฏิบัติแตกต่างกันภายในฟังก์ชั่น
rlbond

4
Bah คนเสียใจ แต่ฉันมีการลบความคิดเห็นของฉันและโพสต์อีกครั้งเพราะของเครื่องยนต์รูปแบบใหม่: มันเป็นเพราะในพารามิเตอร์ฟังก์ชั่นและR() == R(*)() T[] == T*นั่นคือประเภทฟังก์ชั่นเป็นประเภทตัวชี้ฟังก์ชั่นและประเภทอาร์เรย์เป็นประเภทตัวชี้ไปยังองค์ประกอบ อันนี้ครับ มันสามารถทำงานได้โดยA c3((A()));(parens รอบการแสดงออก)
Johannes Schaub - litb

4
ฉันขอถามว่า "'อ่าน 8.5 / 14'" หมายถึงอะไร? มันหมายถึงอะไร? หนังสือ? บทหรือไม่ เว็บไซต์?
AzP

9
@AzP ผู้คนมากมายใน SO มักต้องการการอ้างอิงถึงข้อมูลจำเพาะ C ++ และนั่นคือสิ่งที่ฉันทำที่นี่เพื่อตอบสนองต่อคำขอของ rlbond "โปรดอ้างอิงข้อความเป็นหลักฐาน" ฉันไม่ต้องการที่จะอ้างถึงข้อมูลจำเพาะเพราะมันทำให้ฉันได้รับคำตอบและมันก็เป็นงานที่ต้องทำอีกมากเพื่อให้ทันต่อเหตุการณ์ (ความซ้ำซ้อน)
Johannes Schaub - litb

1
@luca ฉันขอแนะนำให้เริ่มต้นคำถามใหม่เพื่อที่คนอื่น ๆ จะได้ประโยชน์จากคำตอบที่คนให้เช่นกัน
Johannes Schaub - litb

49

ที่ได้รับมอบหมายจะแตกต่างจากการเริ่มต้น

ทั้งสองบรรทัดต่อไปนี้ทำเริ่มต้น การเรียก constructor เดียวเสร็จสิ้นแล้ว:

A a1 = A_factory_func();  // calls copy constructor
A a1(A_factory_func());   // calls copy constructor

แต่มันไม่เท่ากับ:

A a1;                     // calls default constructor
a1 = A_factory_func();    // (assignment) calls operator =

ฉันไม่มีข้อความเพื่อพิสูจน์สิ่งนี้ในขณะนี้ แต่มันเป็นเรื่องง่ายที่จะทดสอบ:

#include <iostream>
using namespace std;

class A {
public:
    A() { 
        cout << "default constructor" << endl;
    }

    A(const A& x) { 
        cout << "copy constructor" << endl;
    }

    const A& operator = (const A& x) {
        cout << "operator =" << endl;
        return *this;
    }
};

int main() {
    A a;       // default constructor
    A b(a);    // copy constructor
    A c = a;   // copy constructor
    c = b;     // operator =
    return 0;
}

2
การอ้างอิงที่ดี: "ภาษาการเขียนโปรแกรม C ++ รุ่นพิเศษ" โดย Bjarne Stroustrup, ส่วน 10.4.4.1 (หน้า 245) อธิบายถึงการเริ่มต้นการคัดลอกและการมอบหมายการคัดลอกและสาเหตุที่แตกต่างกันโดยพื้นฐาน (แม้ว่าทั้งคู่จะใช้ตัวดำเนินการ = เป็นไวยากรณ์)
Naaff

ผู้เยาว์เล็กน้อย แต่ฉันไม่ชอบเมื่อมีคนพูดว่า "A (x)" และ "A a = x" เท่ากัน อย่างเคร่งครัดพวกเขาไม่ได้ ในหลายกรณีพวกเขาจะทำสิ่งเดียวกัน แต่ก็เป็นไปได้ที่จะสร้างตัวอย่างที่ขึ้นอยู่กับการโต้แย้งที่เรียกว่าการก่อสร้างที่แตกต่างกันจริง
Richard Corden

ฉันไม่ได้พูดถึง "ความเท่าเทียมกันทางประโยค" ทั้งสองวิธีของการเริ่มต้นเหมือนกัน
Mehrdad Afshari

@MehrdadAfshari ในรหัสคำตอบของโยฮันเนสคุณจะได้ผลลัพธ์ที่แตกต่างกันตามที่คุณใช้ทั้งสอง
Brian Gordon

1
@BrianGordon ใช่คุณพูดถูก พวกเขาจะไม่เทียบเท่า ฉันได้แสดงความคิดเห็นของ Richard ในการแก้ไขของฉันนานมาแล้ว
Mehrdad Afshari

22

double b1 = 0.5; เป็นการเรียกใช้ตัวสร้างโดยนัย

double b2(0.5); เป็นการโทรที่ชัดเจน

ดูรหัสต่อไปนี้เพื่อดูความแตกต่าง:

#include <iostream>
class sss { 
public: 
  explicit sss( int ) 
  { 
    std::cout << "int" << std::endl;
  };
  sss( double ) 
  {
    std::cout << "double" << std::endl;
  };
};

int main() 
{ 
  sss ddd( 7 ); // calls int constructor 
  sss xxx = 7;  // calls double constructor 
  return 0;
}

หากชั้นเรียนของคุณไม่มี constuctors ชัดเจนกว่าการโทรแบบชัดแจ้งและโดยนัยก็เหมือนกัน


5
+1 คำตอบที่ดี ดีที่จะต้องทราบรุ่นที่ชัดเจน อย่างไรก็ตามมันเป็นเรื่องสำคัญที่จะต้องทราบว่าคุณไม่สามารถมีConstructor Overload ในทั้งสองเวอร์ชันได้พร้อมกัน ดังนั้นมันจะล้มเหลวในการรวบรวมในกรณีที่ชัดเจน หากพวกเขาทั้งสองรวบรวมพวกเขาจะต้องทำงานในทำนองเดียวกัน
Mehrdad Afshari

4

การจัดกลุ่มครั้งแรก: ขึ้นอยู่กับสิ่งที่A_factory_funcส่งคืน บรรทัดแรกเป็นตัวอย่างของการเริ่มต้นการคัดลอกบรรทัดที่สองคือการเริ่มต้นโดยตรง หากA_factory_funcส่งคืนAวัตถุจากนั้นพวกเขาจะเทียบเท่าพวกเขาทั้งสองเรียกตัวสร้างสำเนาสำหรับAมิฉะนั้นรุ่นแรกสร้าง rvalue ประเภทAจากผู้ประกอบการแปลงที่มีอยู่สำหรับประเภทกลับมาของA_factory_funcหรือAก่อสร้างที่เหมาะสมแล้วเรียกตัวสร้างสำเนาเพื่อสร้างa1จากนี้ ชั่วคราว. รุ่นที่สองพยายามค้นหา Constructor ที่เหมาะสมA_factory_funcส่งคืนหรือใช้สิ่งที่ค่าส่งคืนสามารถถูกแปลงเป็นปริยาย

การจัดกลุ่มที่สอง: มีเหตุผลเดียวกันทั้งหมดยกเว้นว่าสิ่งที่สร้างขึ้นในประเภทไม่มีคอนสตรัคเตอร์ที่แปลกใหม่ดังนั้นพวกเขาจึงปฏิบัติเหมือนกัน

การจัดกลุ่มที่สาม: c1เป็นค่าเริ่มต้นเริ่มต้นc2ถูกคัดลอกเริ่มต้นจากค่าเริ่มต้นชั่วคราว สมาชิกใด ๆc1ที่มีประเภทพ็อด (หรือสมาชิกของสมาชิก ฯลฯ ) อาจไม่สามารถเริ่มต้นได้หากผู้ใช้ที่ให้มากับคอนสตรัคเตอร์เริ่มต้น (ถ้ามี) จะไม่เริ่มต้นอย่างชัดเจน สำหรับc2มันขึ้นอยู่กับว่ามีผู้ใช้สร้างตัวคัดลอกและไม่ว่าจะเริ่มต้นอย่างเหมาะสมสมาชิกเหล่านั้น แต่สมาชิกของชั่วคราวจะทั้งหมดจะเริ่มต้น (ศูนย์เริ่มต้นถ้าไม่เริ่มต้นอย่างชัดเจน) ในฐานะที่เป็น litb ด่างc3เป็นกับดัก มันคือการประกาศฟังก์ชั่น


4

จากบันทึก:

[12.2 / 1] Temporaries of class type are created in various contexts: ... and in some initializations (8.5).

เช่นสำหรับการคัดลอกเริ่มต้น

[12.8 / 15] When certain criteria are met, an implementation is allowed to omit the copy construction of a class object ...

กล่าวอีกนัยหนึ่งคอมไพเลอร์ที่ดีจะไม่สร้างสำเนาสำหรับการคัดลอกเริ่มต้นเมื่อสามารถหลีกเลี่ยงได้ แต่มันจะเรียกนวกรรมิกโดยตรง - เช่นเดียวกับการเริ่มต้นโดยตรง

กล่าวอีกนัยหนึ่งการคัดลอกเริ่มต้นก็เหมือนกับการเริ่มต้นโดยตรงในกรณีส่วนใหญ่ <opinion> ที่เขียนโค้ดที่เข้าใจได้ เนื่องจากการกำหนดค่าเริ่มต้นโดยตรงอาจทำให้เกิดการแปลง (และอาจไม่ทราบ) โดยพลการฉันจึงต้องการใช้การกำหนดค่าเริ่มต้นเมื่อเป็นไปได้เสมอ (ด้วยโบนัสที่ดูเหมือนว่าการเริ่มต้น) </opinion>

ความมีไหวพริบทางเทคนิค: [12.2 / 1 ข้อต่อจากด้านบน] Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created.

ดีใจที่ฉันไม่ได้เขียนคอมไพเลอร์ C ++


4

คุณสามารถเห็นความแตกต่างในexplicitและimplicitประเภทของตัวสร้างเมื่อคุณเริ่มต้นวัตถุ:

ชั้นเรียน:

class A
{
    A(int) { }      // converting constructor
    A(int, int) { } // converting constructor (C++11)
};

class B
{
    explicit B(int) { }
    explicit B(int, int) { }
};

และใน main ฟังก์ชั่น:

int main()
{
    A a1 = 1;      // OK: copy-initialization selects A::A(int)
    A a2(2);       // OK: direct-initialization selects A::A(int)
    A a3 {4, 5};   // OK: direct-list-initialization selects A::A(int, int)
    A a4 = {4, 5}; // OK: copy-list-initialization selects A::A(int, int)
    A a5 = (A)1;   // OK: explicit cast performs static_cast

//  B b1 = 1;      // error: copy-initialization does not consider B::B(int)
    B b2(2);       // OK: direct-initialization selects B::B(int)
    B b3 {4, 5};   // OK: direct-list-initialization selects B::B(int, int)
//  B b4 = {4, 5}; // error: copy-list-initialization does not consider B::B(int,int)
    B b5 = (B)1;   // OK: explicit cast performs static_cast
}

โดยค่าเริ่มต้นตัวสร้างเป็นเช่นimplicitนั้นคุณมีสองวิธีในการเริ่มต้น:

A a1 = 1;        // this is copy initialization
A a2(2);         // this is direct initialization

และโดยการกำหนดโครงสร้างexplicitเพียงแค่คุณมีทางเดียวที่จะตรง:

B b2(2);        // this is direct initialization
B b5 = (B)1;    // not problem if you either use of assign to initialize and cast it as static_cast

3

ตอบด้วยความเคารพในส่วนนี้:

A c2 = A (); A c3 (A ());

เนื่องจากคำตอบส่วนใหญ่คือ pre-c ++ 11 ฉันกำลังเพิ่มสิ่งที่ c ++ 11 ต้องพูดเกี่ยวกับสิ่งนี้:

simple-type-specifier (7.1.6.2) หรือ typename-specifier (14.6) ตามด้วยรายการนิพจน์วงเล็บที่อยู่ในวงเล็บจะสร้างค่าของประเภทที่ระบุที่กำหนดให้กับรายการนิพจน์ หากรายการนิพจน์เป็นนิพจน์เดียวนิพจน์การแปลงประเภทจะเทียบเท่า (ตามที่กำหนดและหากกำหนดไว้ในความหมาย) กับนิพจน์การโยนที่สอดคล้องกัน (5.4) หากประเภทที่ระบุเป็นประเภทชั้นเรียนประเภทชั้นเรียนจะต้องสมบูรณ์ หากรายการนิพจน์ระบุมากกว่าค่าเดียวประเภทจะเป็นคลาสที่มีคอนสตรัคเตอร์ที่ประกาศอย่างเหมาะสม (8.5, 12.1) และนิพจน์ T (x1, x2, ... ) เทียบเท่ากับการประกาศ T t (x1, x2, ... ); สำหรับตัวแปรชั่วคราวที่ประดิษฐ์ขึ้นบางตัว t โดยผลลัพธ์จะเป็นค่าของ t ว่าเป็น prvalue

ดังนั้นการเพิ่มประสิทธิภาพหรือไม่พวกเขาจะเทียบเท่าตามมาตรฐาน โปรดทราบว่านี่เป็นไปตามคำตอบอื่น ๆ ที่ได้กล่าวมา เพียงแค่พูดถึงสิ่งที่มาตรฐานพูดถึงเพื่อความถูกต้อง


รายการนิพจน์ตัวอย่าง '' ของคุณไม่มีการระบุมากกว่าค่าเดียว " สิ่งนี้เกี่ยวข้องกันอย่างไร
underscore_d

0

กรณีเหล่านี้จำนวนมากขึ้นอยู่กับการใช้งานของวัตถุดังนั้นจึงยากที่จะให้คำตอบที่เป็นรูปธรรม

พิจารณากรณี

A a = 5;
A a(5);

ในกรณีนี้สมมติว่าตัวดำเนินการกำหนดค่าที่เหมาะสม & ตัวสร้างการเริ่มต้นซึ่งยอมรับอาร์กิวเมนต์จำนวนเต็มเดียววิธีที่ฉันใช้วิธีดังกล่าวมีผลต่อพฤติกรรมของแต่ละบรรทัด มันเป็นเรื่องธรรมดา แต่สำหรับหนึ่งในนั้นที่จะเรียกคนอื่น ๆ ในการดำเนินการเพื่อกำจัดรหัสที่ซ้ำกัน (แม้ว่าในกรณีที่ง่ายเช่นนี้จะไม่มีวัตถุประสงค์ที่แท้จริง)

แก้ไข: ดังที่กล่าวไว้ในคำตอบอื่น ๆ บรรทัดแรกจะเรียกว่าตัวสร้างสำเนา พิจารณาความคิดเห็นที่เกี่ยวข้องกับผู้ประกอบการที่ได้รับมอบหมายว่าเป็นพฤติกรรมที่เกี่ยวข้องกับการมอบหมายให้อยู่คนเดียว

ที่กล่าวมาว่าคอมไพเลอร์ปรับรหัสให้เหมาะสมจะมีผลกระทบอย่างไร ถ้าฉันมีตัวสร้างการเริ่มต้นเรียกใช้ตัวดำเนินการ "=" - ถ้าคอมไพเลอร์ไม่ทำการปรับให้เหมาะสมบรรทัดบนสุดจะทำการกระโดด 2 ครั้งซึ่งตรงข้ามกับอันใดอันหนึ่งในบรรทัดล่างสุด

ตอนนี้สำหรับสถานการณ์ที่พบบ่อยที่สุดคอมไพเลอร์ของคุณจะปรับให้เหมาะสมผ่านกรณีเหล่านี้และกำจัดความไร้ประสิทธิภาพประเภทนี้ ดังนั้นสถานการณ์ที่แตกต่างกันทั้งหมดที่คุณอธิบายจะมีผลเหมือนกัน ถ้าคุณต้องการที่จะเห็นสิ่งที่กำลังทำคุณสามารถดูรหัสวัตถุหรือผลลัพธ์ประกอบของคอมไพเลอร์ของคุณ


มันไม่ได้เป็นการเพิ่มประสิทธิภาพ คอมไพเลอร์ต้องเรียกนวกรรมิกเหมือนกันทั้งสองกรณี เป็นผลให้ไม่มีของพวกเขาจะรวบรวมถ้าคุณเพียงแค่ต้องและไม่มีการoperator =(const int) A(const int)ดูคำตอบของ @ jia3ep สำหรับรายละเอียดเพิ่มเติม
Mehrdad Afshari

ฉันเชื่อว่าคุณถูกต้องจริง อย่างไรก็ตามมันจะรวบรวมได้ดีโดยใช้ตัวสร้างสำเนาเริ่มต้น
dborba

นอกจากนี้อย่างที่ฉันได้กล่าวไปแล้วมันเป็นเรื่องธรรมดาที่จะมีตัวสร้างการคัดลอกเรียกผู้ประกอบการที่ได้รับมอบหมาย
dborba

0

นี่คือจากภาษาโปรแกรม C ++ โดย Bjarne Stroustrup:

เริ่มต้นด้วยการ = ถือว่าเป็นเริ่มต้นสำเนา โดยหลักการแล้วสำเนาของ initializer (วัตถุที่เราคัดลอกมา) จะถูกวางลงในวัตถุเริ่มต้น อย่างไรก็ตามสำเนาดังกล่าวอาจถูกปรับให้เหมาะสม (ลบออก) และการดำเนินการย้าย (ตามความหมายของการย้าย) อาจถูกนำมาใช้หาก initializer นั้นเป็นค่า rvalue การปล่อยให้ = ทำให้การกำหนดค่าเริ่มต้นชัดเจน เริ่มต้นอย่างชัดเจนเป็นที่รู้จักกันเริ่มต้นโดยตรง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.