เหตุใดจึงไม่มีการกำหนดค่าการย้าย / ย้ายตัวสร้างเริ่มต้น


89

ฉันเป็นโปรแกรมเมอร์ธรรมดา ๆ ตัวแปรสมาชิกชั้นเรียนของฉันส่วนใหญ่มักประกอบด้วยประเภท POD และ STL-container ด้วยเหตุนี้ฉันจึงแทบไม่ต้องเขียนตัวดำเนินการกำหนดหรือคัดลอกตัวสร้างเนื่องจากจะใช้งานโดยค่าเริ่มต้น

เพิ่มสิ่งนี้ถ้าฉันใช้std::moveกับวัตถุที่ไม่สามารถเคลื่อนย้ายได้มันจะใช้ตัวดำเนินการกำหนดหมายความว่าstd::moveปลอดภัยอย่างสมบูรณ์

เนื่องจากฉันเป็นโปรแกรมเมอร์ธรรมดาฉันจึงต้องการใช้ประโยชน์จากความสามารถในการย้ายโดยไม่ต้องเพิ่มตัวสร้างการย้าย / ตัวดำเนินการกำหนดให้กับทุกชั้นเรียนที่ฉันเขียนเนื่องจากคอมไพเลอร์สามารถนำไปใช้เป็น " this->member1_ = std::move(other.member1_);..."

แต่มันไม่มี (อย่างน้อยก็ไม่ใช่ใน Visual 2010) มีเหตุผลพิเศษสำหรับสิ่งนี้หรือไม่?

ที่สำคัญกว่า; มีวิธีใดบ้างที่จะหลีกเลี่ยงสิ่งนี้?

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


5
คุณรู้ว่าคุณสามารถให้คอมไพเลอร์สร้าง ctor การย้ายเริ่มต้นได้
aaronman

3
std :: move ไม่ได้ทำการเคลื่อนไหวเพียงแค่ร่ายจากค่า l เป็นค่า r การย้ายยังคงดำเนินการโดยตัวสร้างการย้าย
Owen Delahoy

1
คุณกำลังพูดถึงMyClass::MyClass(Myclass &&) = default;?
Sandburg

ใช่แล้ววันนี้ :)
Viktor Sehr

คำตอบ:


76

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

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับประวัติของปัญหาโปรดดูรายการเอกสาร WG21 ปี 2010และค้นหา "mov"

ข้อกำหนดปัจจุบัน (N3225 ตั้งแต่เดือนพฤศจิกายน) ระบุ (N3225 12.8 / 8):

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

  • X ไม่มีตัวสร้างการคัดลอกที่ผู้ใช้ประกาศและ

  • X ไม่มีตัวดำเนินการกำหนดสำเนาที่ผู้ใช้ประกาศ

  • X ไม่มีตัวดำเนินการกำหนดการย้ายที่ผู้ใช้ประกาศ

  • X ไม่มีตัวทำลายที่ผู้ใช้ประกาศและ

  • ตัวสร้างการย้ายจะไม่ถูกกำหนดโดยปริยายว่าถูกลบ

มีภาษาที่คล้ายกันใน 12.8 / 22 ระบุเมื่อตัวดำเนินการกำหนดการย้ายถูกประกาศโดยปริยายว่าเป็นค่าเริ่มต้น คุณสามารถค้นหารายการสินค้าทั้งหมดของการเปลี่ยนแปลงที่เกิดขึ้นกับสนับสนุนข้อกำหนดปัจจุบันของรุ่นย้ายนัยในN3203: กระชับเงื่อนไขสำหรับการสร้างการเคลื่อนไหวโดยปริยาย ซึ่งส่วนใหญ่จะขึ้นอยู่กับหนึ่งในมติที่เสนอโดยกระดาษ Bjarne Stroustrup ของN3201: การย้ายขวาพร้อม


4
ฉันเขียนบทความเล็ก ๆ พร้อมแผนภาพบางส่วนที่อธิบายความสัมพันธ์ของตัวสร้าง / การกำหนดโดยนัย (ย้าย) ที่นี่: mmocny.wordpress.com/2010/12/09/implicit-move-wont-go
mmocny

เอ่อดังนั้นเมื่อใดก็ตามที่ฉันต้องกำหนดตัวทำลายเปล่าในคลาสพื้นฐานของโพลีมอร์ฟิคเพียงเพื่อระบุว่าเป็นเสมือนฉันต้องกำหนดตัวสร้างการย้ายและตัวดำเนินการกำหนดอย่างชัดเจนด้วย :(
someguy

@ James McNellis: นั่นคือสิ่งที่ฉันเคยลองก่อนหน้านี้ แต่คอมไพเลอร์ดูเหมือนจะไม่ชอบ ฉันกำลังจะไปโพสต์ข้อความแสดงข้อผิดพลาดในเรื่องนี้ตอบมาก cannot be defaulted *in the class body*แต่หลังจากที่พยายามที่จะทำให้เกิดข้อผิดพลาดที่ฉันรู้ว่ามันกล่าวว่า ดังนั้นฉันจึงกำหนดตัวทำลายภายนอกและใช้งานได้ :) ฉันคิดว่ามันแปลกนิดหน่อย ใครมีคำอธิบาย? คอมไพเลอร์คือ gcc 4.6.1
someguy

3
บางทีเราอาจได้รับการอัปเดตสำหรับคำตอบนี้ในตอนนี้ว่า C ++ 11 ได้รับการให้สัตยาบันแล้ว? อยากรู้ว่าพฤติกรรมใดชนะ
Joseph Garvin

2
@Guy Avraham: ฉันคิดว่าสิ่งที่ฉันพูด (เป็นเวลา 7 ปีแล้ว) คือถ้าฉันมีตัวทำลายล้างที่ผู้ใช้ประกาศ (แม้กระทั่งเสมือนที่ว่างเปล่า) จะไม่มีการประกาศตัวสร้างการย้ายโดยปริยายว่าเป็นค่าเริ่มต้น ฉันคิดว่ามันจะส่งผลให้เกิดการคัดลอกความหมาย? (ฉันไม่ได้สัมผัส C ++ มาหลายปีแล้ว) จากนั้น James McNellis ให้ความเห็นว่าvirtual ~D() = default;ควรใช้งานได้และยังคงอนุญาตให้มีตัวสร้างการย้ายโดยปริยาย
someguy

13

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

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

ถ้านิยามของคลาส X ไม่ได้ประกาศตัวสร้างการย้ายอย่างชัดเจนตัวสร้างการย้ายจะถูกประกาศโดยปริยายว่าเป็นค่าเริ่มต้นก็ต่อเมื่อ
- X ไม่มีตัวสร้างการคัดลอกที่ผู้ใช้ประกาศไว้เท่านั้น
- X ไม่มีตัวดำเนินการกำหนดสำเนาที่ผู้ใช้ประกาศ ,
- X ไม่มีตัวดำเนินการ กำหนดการย้ายที่ผู้ใช้ประกาศ,
- X ไม่มีตัวทำลายที่ผู้ใช้ประกาศและ
- ตัวสร้างการย้ายจะไม่ถูกกำหนดโดยปริยายว่าถูกลบ

นั่นไม่ใช่ทั้งหมดที่มีอยู่ในเรื่องนี้ สามารถประกาศ ctor ได้ แต่ยังคงกำหนดเป็นลบ:

ตัวสร้างการคัดลอก / ย้ายที่ประกาศโดยนัยเป็นสมาชิกสาธารณะแบบอินไลน์ของคลาส ตัวสร้างการคัดลอก / ย้ายเริ่มต้นสำหรับคลาส X ถูกกำหนดให้เป็นลบ (8.4.3) ถ้า X มี:

- สมาชิกตัวแปรที่มีตัวสร้างที่สอดคล้องกันที่ไม่สำคัญและ X เป็นคลาสที่เหมือนสหภาพ -
สมาชิกข้อมูลที่ไม่คงที่ของคลาสประเภท M (หรืออาร์เรย์ของมัน) ที่ไม่สามารถคัดลอก / ย้ายได้เนื่องจากความละเอียดเกิน (13.3) ตามที่ นำไปใช้กับตัวสร้างที่สอดคล้องกันของ M ส่งผลให้เกิดความไม่ชัดเจนหรือฟังก์ชันที่ถูกลบหรือไม่สามารถเข้าถึงได้จากตัวสร้างเริ่มต้น
- คลาส B พื้นฐานโดยตรงหรือเสมือนที่ไม่สามารถคัดลอก / ย้ายได้เนื่องจากความละเอียดเกิน (13.3) ตามที่ใช้กับตัวสร้างที่สอดคล้องกันของ B ส่งผลให้เกิดความคลุมเครือหรือฟังก์ชันที่ถูกลบหรือไม่สามารถเข้าถึงได้จากตัวสร้างเริ่มต้น
- คลาสพื้นฐานโดยตรงหรือเสมือนหรือสมาชิกข้อมูลที่ไม่คงที่ของชนิดที่มีตัวทำลายที่ถูกลบหรือไม่สามารถเข้าถึงได้จากตัวสร้างเริ่มต้น
- สำหรับตัวสร้างการคัดลอกสมาชิกข้อมูลที่ไม่คงที่ของชนิดการอ้างอิง rvalue หรือ
- สำหรับตัวสร้างการย้ายสมาชิกข้อมูลที่ไม่คงที่หรือคลาสฐานโดยตรงหรือเสมือนที่มีประเภทที่ไม่มีตัวสร้างการย้ายและไม่สำคัญ คัดลอกได้


ร่างการทำงานปัจจุบันอนุญาตให้มีการเคลื่อนย้ายโดยปริยายภายใต้เงื่อนไขบางประการและฉันคิดว่าการแก้ปัญหาส่วนใหญ่ตอบข้อกังวลของอับราฮัม
James McNellis

ฉันไม่แน่ใจว่าฉันเข้าใจว่าการเคลื่อนไหวใดที่อาจแตกหักในตัวอย่างระหว่าง Tweak 2 และ Tweak 3 คุณช่วยอธิบายได้ไหม
Matthieu M.

@ Matthieu M: ทั้ง Tweak 2 และ Tweak 3 เสียและในรูปแบบที่คล้ายกันจริงๆ ใน Tweak 2 มีสมาชิกส่วนตัวที่มีค่าคงที่ที่สามารถหักได้โดยย้าย ctor ใน Tweak 3 ชั้นไม่ได้มีสมาชิกส่วนตัวของตัวเองแต่เพราะใช้มรดกเอกชนประชาชนและสมาชิกในการป้องกันของฐานเป็นสมาชิกของเอกชนที่ได้มานำไปสู่ปัญหาเดียวกัน
Jerry Coffin

1
ฉันไม่เข้าใจจริงๆว่าตัวสร้างการเคลื่อนไหวจะทำลายคลาสไม่แปรผันTweak2ได้อย่างไร ฉันคิดว่ามันมีบางสิ่งบางอย่างจะทำอย่างไรกับความจริงที่ว่าNumberจะถูกย้ายไปและvectorจะได้รับการคัดลอก ... แต่ผมไม่แน่ใจว่า: / Tweak3ฉันจะเข้าใจปัญหาที่เกิดขึ้นจะไปน้ำตก
Matthieu M.

ลิงค์ที่คุณให้ดูเหมือนจะตาย?
Wolf

8

(ตอนนี้ฉันกำลังทำงานกับมาโครโง่ ๆ ... )

ใช่ฉันไปเส้นทางนั้นด้วย นี่คือมาโครของคุณ:

// detail/move_default.hpp
#ifndef UTILITY_DETAIL_MOVE_DEFAULT_HPP
#define UTILITY_DETAIL_MOVE_DEFAULT_HPP

#include <boost/preprocessor.hpp>

#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE(pR, pData, pBase) pBase(std::move(pOther))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE(pR, pData, pBase) pBase::operator=(std::move(pOther));

#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR(pR, pData, pMember) pMember(std::move(pOther.pMember))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT(pR, pData, pMember) pMember = std::move(pOther.pMember);

#define UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)                                               \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases))                      \
        ,                                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers))                         \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases)  \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers)     \
                                                                                                        \
            return *this;                                                                               \
        }

#define UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)                                                   \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases))                      \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases)  \
                                                                                                        \
            return *this;                                                                               \
        }

#define UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)                                               \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers))                         \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers)     \
                                                                                                        \
            return *this;                                                                               \
        }

#endif

// move_default.hpp
#ifndef UTILITY_MOVE_DEFAULT_HPP
#define UTILITY_MOVE_DEFAULT_HPP

#include "utility/detail/move_default.hpp"

// move bases and members
#define UTILITY_MOVE_DEFAULT(pT, pBases, pMembers) UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)

// base only version
#define UTILITY_MOVE_DEFAULT_BASES(pT, pBases) UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)

// member only version
#define UTILITY_MOVE_DEFAULT_MEMBERS(pT, pMembers) UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)

#endif

(ฉันได้ลบความคิดเห็นจริงซึ่งเป็นความยาวและสารคดี)

คุณระบุฐานและ / หรือสมาชิกในชั้นเรียนของคุณเป็นรายการตัวประมวลผลล่วงหน้าตัวอย่างเช่น:

#include "move_default.hpp"

struct foo
{
    UTILITY_MOVE_DEFAULT_MEMBERS(foo, (x)(str));

    int x;
    std::string str;
};

struct bar : foo, baz
{
    UTILITY_MOVE_DEFAULT_BASES(bar, (foo)(baz));
};

struct baz : bar
{
    UTILITY_MOVE_DEFAULT(baz, (bar), (ptr));

    void* ptr;
};

และตัวดำเนินการย้ายตัวสร้างและตัวดำเนินการย้ายออกมา

(นอกจากนี้ถ้าใครรู้ว่าฉันสามารถรวมรายละเอียดเป็นมาโครเดียวได้อย่างไรมันก็จะบวม)


ขอบคุณมากของฉันค่อนข้างคล้ายกันยกเว้นว่าฉันต้องส่งจำนวนตัวแปรสมาชิกเป็นอาร์กิวเมนต์ (ซึ่งแย่มาก)
Viktor Sehr

1
@ วิคเตอร์: ไม่มีปัญหา ถ้ายังไม่สายเกินไปฉันคิดว่าคุณควรทำเครื่องหมายคำตอบอื่นว่ายอมรับ ของฉันเป็นมากกว่า "อย่างไรก็ตามนี่คือวิธี" และไม่ใช่คำตอบสำหรับคำถามที่แท้จริงของคุณ
GManNickG

1
หากฉันอ่านมาโครของคุณอย่างถูกต้องทันทีที่คอมไพเลอร์ของคุณใช้สมาชิกการย้ายที่ผิดนัดตัวอย่างด้านบนของคุณจะไม่สามารถคัดลอกได้ การสร้างสมาชิกสำเนาโดยนัยจะถูกยับยั้งเมื่อมีสมาชิกย้ายที่ประกาศอย่างชัดเจนอยู่
Howard Hinnant

@ Howard: ไม่เป็นไรมันเป็นวิธีแก้ปัญหาชั่วคราวจนกว่าจะถึงตอนนั้น :)
GManNickG

GMan: มาโครนี้จะเพิ่ม moveconstructor \ assign หากคุณมีฟังก์ชัน swap:
Viktor Sehr

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