วิธีการคัดลอกเมื่อผูกมัด?


10

ฉันกำลังสร้างคลาสของการผูกมัดประเภทเช่นตัวอย่างเล็ก ๆ ด้านล่าง ดูเหมือนว่าเมื่อการเชื่อมโยงสมาชิกฟังก์ชันแล้วตัวสร้างการคัดลอกจะถูกเรียกใช้ มีวิธีกำจัด call constructor ของการคัดลอกหรือไม่? ในตัวอย่างของเล่นของฉันด้านล่างเป็นที่ชัดเจนว่าฉันกำลังจัดการกับขมับเท่านั้นและดังนั้นจึงมี "ควร" (อาจไม่ได้ตามมาตรฐาน ตัวเลือกที่ดีที่สุดอันดับที่สองคือการคัดลอกตัวเลือกจะเป็นตัวสร้างการย้ายที่จะถูกเรียก แต่ไม่ใช่ในกรณีนี้

class test_class {
    private:
    int i = 5;
    public:
    test_class(int i) : i(i) {}
    test_class(const test_class& t) {
        i = t.i;
        std::cout << "Copy constructor"<< std::endl;
    }
    test_class(test_class&& t) {
        i = t.i;
        std::cout << "Move constructor"<< std::endl;
    }
    auto& increment(){
        i++;
        return *this;
    }
};
int main()
{
    //test_class a{7};
    //does not call copy constructor
    auto b = test_class{7};
    //calls copy constructor
    auto b2 = test_class{7}.increment();
    return 0;
}

แก้ไข: คำชี้แจงบางอย่าง 1. สิ่งนี้ไม่ได้ขึ้นอยู่กับระดับการปรับให้เหมาะสม 2. ในรหัสจริงของฉันฉันมีวัตถุที่ซับซ้อน (เช่นจัดสรรฮีป) มากกว่า ints


ระดับการเพิ่มประสิทธิภาพใดที่คุณใช้ในการรวบรวม
JVApen

2
auto b = test_class{7};ไม่เรียกตัวสร้างสำเนาเนื่องจากจริง ๆ แล้วเทียบเท่าtest_class b{7};และคอมไพเลอร์ฉลาดพอที่จะรับรู้กรณีนี้และสามารถคัดลอกได้อย่างง่ายดาย b2สามารถเดียวกันไม่ได้ทำเพื่อ
โปรแกรมเมอร์บางคนเพื่อน

ในตัวอย่างที่แสดงอาจมีความแตกต่างระหว่างการย้ายและการคัดลอกไม่มากหรือไม่และทุกคนตระหนักถึงสิ่งนี้ หากคุณติดอะไรที่เหมือนเวกเตอร์ตัวใหญ่ในนั้นอาจเป็นเรื่องที่แตกต่างออกไป โดยปกติการย้ายจะใช้งานได้ดีสำหรับทรัพยากรที่ใช้ชนิด (เช่นใช้ฮีป mem จำนวนมาก ฯลฯ ) - เป็นกรณีนี้หรือไม่
darune

ตัวอย่างมีลักษณะที่วางแผนไว้ คุณมี I / O ( std::cout) ในสำเนา ctor ของคุณหรือไม่? ก็ไม่มีควรคัดลอกควรปรับให้เหมาะสม
rustyx

@rustyx, ลบ std :: cout และทำให้ constructor ของ copy ชัดเจน สิ่งนี้แสดงให้เห็นว่าการคัดลอกข้อมูลไม่ใช่ std :: cout ขึ้นอยู่กับ
DDaniel

คำตอบ:


7
  1. คำตอบบางส่วน (ไม่สร้างb2ขึ้น แต่เปลี่ยนโครงสร้างการคัดลอกเป็นการก่อสร้างแบบย้าย): คุณสามารถโอเวอร์โหลดincrementฟังก์ชันสมาชิกในหมวดค่าของอินสแตนซ์ที่เกี่ยวข้อง:

    auto& increment() & {
        i++;
        return *this;
    }
    
    auto&& increment() && {
        i++;
       return std::move(*this);
    }

    สาเหตุนี้

    auto b2 = test_class{7}.increment();

    เพื่อย้าย - สร้างb2เพราะtest_class{7}เป็นชั่วคราวและ&&เกินพิกัดของtest_class::incrementถูกเรียก

  2. สำหรับการก่อสร้างในสถานที่จริง (เช่นไม่ใช่การก่อสร้างแบบเคลื่อนที่) คุณสามารถเปลี่ยนฟังก์ชั่นสมาชิกพิเศษและไม่ใช่สมาชิกพิเศษให้เป็นconstexprเวอร์ชั่นได้ จากนั้นคุณสามารถทำได้

    constexpr auto b2 = test_class{7}.increment();

    และคุณไม่ต้องย้ายหรือก่อสร้างคัดลอกที่จะจ่าย เห็นได้ชัดว่าเป็นไปได้สำหรับtest_classสถานการณ์ทั่วไป แต่ไม่ใช่สำหรับสถานการณ์ทั่วไปที่ไม่อนุญาตให้ใช้กับconstexprฟังก์ชั่นสมาชิก


ทางเลือกที่สองยังทำให้b2ไม่สามารถแก้ไขได้
โปรแกรมเมอร์บางคนเพื่อน

1
@Someprogrammerdude เป็นจุดที่ดี ฉันคิดว่าconstinitน่าจะเป็นวิธีที่จะไปที่นั่น
lubgr

เครื่องหมายแอมเปอร์แซนด์วัตถุประสงค์ของชื่อฟังก์ชั่นคืออะไร? ฉันไม่เคยเห็นแบบนั้นมาก่อน
ฟิลิปเนลสัน

1
@PhilipNelson พวกเขาเป็นผู้คัดเลือกและสามารถกำหนดสิ่งthisที่เหมือนกับconstฟังก์ชั่นสมาชิกได้
kmdreko

1

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


วิธีที่ง่ายที่สุดน่าจะทำให้ตัวสร้างสำเนาถูกปรับให้เหมาะสมที่สุด การตั้งค่ามีการปรับค่าให้เหมาะสมโดยคอมไพเลอร์ซึ่งเป็นเพียงการปรับค่าstd::coutที่ไม่สามารถทำได้

test_class(const test_class& t) = default;

(หรือเพียงแค่ลบทั้งตัวคัดลอกและตัวสร้างการย้าย)

ตัวอย่างสด


เนื่องจากปัญหาของคุณเป็นแบบอ้างอิงการอ้างอิงอาจไม่ส่งคืนการอ้างอิงไปยังวัตถุถ้าคุณต้องการหยุดการคัดลอกด้วยวิธีนี้

  void increment();
};

auto b = test_class{7};//does not call copy constructor
b.increment();//does not call copy constructor

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

auto b2 = []{test_class tmp{7}; tmp.increment().increment().increment(); return tmp;}(); //<-- b2 becomes 10 - copy constructor not called

วิธีที่สี่คือการใช้การย้ายแทนทั้งเรียกอย่างชัดเจน

auto b2 = std::move(test_class{7}.increment());

หรือเท่าที่เห็นในคำตอบนี้


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