เหตุใดเราจึงใช้ "std :: move" บนวัตถุ "const" ได้


113

ใน C ++ 11 เราสามารถเขียนโค้ดนี้:

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11

เมื่อฉันเรียกstd::moveมันหมายความว่าฉันต้องการย้ายวัตถุกล่าวคือฉันจะเปลี่ยนวัตถุ การย้ายconstวัตถุเป็นเรื่องที่ไม่สมเหตุสมผลเหตุใดจึงstd::moveไม่ จำกัด พฤติกรรมนี้ มันจะเป็นกับดักในอนาคตใช่ไหม?

ที่นี่หมายถึงกับดักตามที่ Brandon กล่าวไว้ในความคิดเห็น:

"ฉันคิดว่าเขาหมายถึงมัน" กับดัก "เขาส่อเสียดส่อเสียดเพราะถ้าเขาไม่รู้ตัวเขาก็จบลงด้วยการลอกเลียนแบบซึ่งไม่ใช่สิ่งที่เขาตั้งใจไว้"

ในหนังสือ 'Effective Modern C ++' โดย Scott Meyers เขายกตัวอย่าง:

class Annotation {
public:
    explicit Annotation(const std::string text)
     : value(std::move(text)) //here we want to call string(string&&),
                              //but because text is const, 
                              //the return type of std::move(text) is const std::string&&
                              //so we actually called string(const string&)
                              //it is a bug which is very hard to find out
private:
    std::string value;
};

หากstd::moveถูกห้ามไม่ให้ใช้งานกับconstวัตถุเราสามารถค้นหาจุดบกพร่องได้อย่างง่ายดายใช่ไหม?


2
แต่ลองขยับดูสิ ลองเปลี่ยนสถานะ std::moveโดยตัวมันเองไม่ได้ทำอะไรกับวัตถุ อาจมีคนโต้แย้งstd::moveคือชื่อไม่ดี
juanchopanza

3
มันไม่ได้เคลื่อนไหวอะไรจริง ทั้งหมดที่ทำก็คือการอ้างอิง rvalue ลองCAT cat2 = std::move(cat);สมมติว่าCATรองรับการมอบหมายการย้ายตามปกติ
WhozCraig

11
std::moveเป็นเพียงนักแสดง แต่ไม่ได้เคลื่อนไหวอะไรเลย
Red Alert

2
@WhozCraig: ระวังเนื่องจากโค้ดที่คุณโพสต์รวบรวมและดำเนินการโดยไม่มีการเตือนทำให้เข้าใจผิด
Mooing Duck

1
@MooingDuck ไม่เคยบอกว่าจะไม่รวบรวม มันใช้งานได้เพราะเปิดใช้งาน copy-ctor เริ่มต้นเท่านั้น บีบและล้อหลุด
WhozCraig

คำตอบ:


55
struct strange {
  mutable size_t count = 0;
  strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

ที่นี่เราเห็นการใช้งานstd::moveบนไฟล์T const. มันส่งคืนไฟล์T const&&. เรามีตัวสร้างการย้ายสำหรับstrangeที่ใช้ประเภทนี้

และก็เรียกว่า

ตอนนี้เป็นเรื่องจริงที่ประเภทแปลก ๆ นี้หายากเกินกว่าข้อบกพร่องที่คุณเสนอจะแก้ไขได้

แต่ในทางกลับกันสิ่งที่มีอยู่จะstd::moveทำงานได้ดีกว่าในรหัสทั่วไปโดยที่คุณไม่รู้ว่าประเภทที่คุณกำลังใช้งานเป็น a Tหรือ a T const.


3
+1 เพื่อเป็นคำตอบแรกที่พยายามอธิบายว่าทำไมคุณถึงต้องการเรียกstd::moveใช้constวัตถุ
Chris Drew

+1 const T&&สำหรับการแสดงฟังก์ชั่นการถ่าย นี่เป็นการแสดงออกถึง "โปรโตคอล API" ประเภท "ฉันจะใช้ rvalue-ref แต่ฉันสัญญาว่าจะไม่แก้ไข" ฉันเดาว่านอกเหนือจากเมื่อใช้ mutable แล้วมันก็ผิดปกติ บางทีกรณีการใช้งานอื่นคือสามารถใช้forward_as_tupleกับเกือบทุกอย่างและใช้ในภายหลัง
F Pereira

107

มีเคล็ดลับที่คุณมองข้ามไปนั่นคือสิ่งที่std::move(cat) ไม่ได้เคลื่อนไหวอะไรเลย เป็นเพียงการบอกให้คอมไพเลอร์พยายามที่จะย้าย อย่างไรก็ตามเนื่องจากชั้นเรียนของคุณไม่มีตัวสร้างที่ยอมรับ a const CAT&&จึงจะใช้ตัวconst CAT&สร้างการคัดลอกโดยนัยแทนและคัดลอกอย่างปลอดภัย ไม่มีอันตรายไม่มีกับดัก หากตัวสร้างการคัดลอกถูกปิดใช้งานไม่ว่าด้วยเหตุผลใดก็ตามคุณจะได้รับข้อผิดพลาดของคอมไพเลอร์

struct CAT
{
   CAT(){}
   CAT(const CAT&) {std::cout << "COPY";}
   CAT(CAT&&) {std::cout << "MOVE";}
};

int main() {
    const CAT cat;
    CAT cat2 = std::move(cat);
}

พิมพ์ไม่COPYMOVE

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

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


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

นอกจากนี้ยังควรค่าแก่การกล่าวถึงว่าตัวสร้างการคัดลอกที่ต้องการค่าที่ไม่constคุ้มค่าก็จะไม่ช่วยเช่นกัน [class.copy] §8: "มิฉะนั้นตัวสร้างสำเนาที่ประกาศโดยปริยายจะมีแบบฟอร์มX::X(X&)"
Deduplicator

9
ฉันไม่คิดว่าเขาหมายถึง "กับดัก" ในแง่คอมพิวเตอร์ / การประกอบ ฉันคิดว่าเขาหมายถึงมัน "กับดัก" เขาส่อเสียดส่อเสียดเพราะถ้าเขาไม่รู้เขาจะจบลงด้วยสำเนาที่ไม่ใช่สิ่งที่เขาตั้งใจ ฉันเดา ..
Brandon

ขอให้คุณได้รับการโหวตเพิ่มขึ้นอีก 1 ครั้งและฉันจะได้รับอีก 4 คะแนนสำหรับป้ายทอง ;)
Yakk - Adam Nevraumont

สูงห้าในตัวอย่างรหัสนั้น เข้าประเด็นได้ดีจริงๆ
Brent Writes Code

20

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

template <class C1, class C2>
C1
move_each(C2&& c2)
{
    return C1(std::make_move_iterator(c2.begin()),
              std::make_move_iterator(c2.end()));
}

เจ๋งตอนนี้ฉันสามารถสร้าง a vector<string>จาก a ได้อย่างมีประสิทธิภาพdeque<string>และแต่ละคนstringจะถูกย้ายในกระบวนการ

แต่ถ้าฉันต้องการย้ายจาก a map?

int
main()
{
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
    for (auto const& p : v)
        std::cout << "{" << p.first << ", " << p.second << "} ";
    std::cout << '\n';
}

หากstd::moveยืนยันในการไม่constโต้แย้งการสร้างอินสแตนซ์ข้างต้นmove_eachจะไม่รวบรวมเนื่องจากกำลังพยายามย้าย a const int(the key_typeof the map) แต่รหัสนี้ไม่สนใจว่าจะไม่สามารถย้ายไฟล์key_type. ต้องการย้ายmapped_type( std::string) ด้วยเหตุผลด้านประสิทธิภาพ

สำหรับตัวอย่างนี้และตัวอย่างอื่น ๆ อีกนับไม่ถ้วนเช่นในการเข้ารหัสทั่วไปซึ่งstd::moveเป็นการร้องขอให้ย้ายไม่ใช่ความต้องการที่จะย้าย


2

ฉันมีความกังวลเช่นเดียวกับ OP

std :: move ไม่เคลื่อนย้ายวัตถุและไม่รับประกันว่าวัตถุนั้นเคลื่อนที่ได้ แล้วทำไมถึงเรียกว่าย้าย?

ฉันคิดว่าการไม่สามารถเคลื่อนย้ายได้อาจเป็นหนึ่งในสองสถานการณ์ต่อไปนี้:

1. ประเภทการเคลื่อนที่คือ const

เหตุผลที่เรามีคำหลัก const ในภาษาคือเราต้องการให้คอมไพเลอร์ป้องกันการเปลี่ยนแปลงใด ๆ กับวัตถุที่กำหนดให้เป็น const ยกตัวอย่างในหนังสือของ Scott Meyers:

    class Annotation {
    public:
     explicit Annotation(const std::string text)
     : value(std::move(text)) // "move" text into value; this code
     {  } // doesn't do what it seems to!    
     
    private:
     std::string value;
    };

แท้จริงแล้วหมายถึงอะไร? ย้ายสตริง const ไปที่ value member - อย่างน้อยนั่นคือความเข้าใจของฉันก่อนที่จะอ่านคำอธิบาย

หากภาษาตั้งใจที่จะไม่ย้ายหรือไม่รับประกันการย้ายสามารถใช้ได้เมื่อเรียก std :: move () แสดงว่าทำให้เข้าใจผิดอย่างแท้จริงเมื่อใช้การย้ายคำ

หากภาษากระตุ้นให้คนที่ใช้ std :: move มีประสิทธิภาพที่ดีขึ้นก็ต้องป้องกันกับดักเช่นนี้โดยเร็วที่สุดโดยเฉพาะอย่างยิ่งสำหรับความขัดแย้งตามตัวอักษรที่ชัดเจนประเภทนี้

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

2. วัตถุไม่มีตัวสร้างการเคลื่อนที่

โดยส่วนตัวแล้วฉันคิดว่านี่เป็นเรื่องที่แยกออกจากความกังวลของ OP อย่างที่ Chris Drew กล่าวไว้

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


1

ฉันแปลกใจที่ไม่มีใครพูดถึงความเข้ากันได้แบบย้อนหลังของเรื่องนี้ ฉันเชื่อว่าstd::moveถูกออกแบบมาโดยเจตนาเพื่อทำสิ่งนี้ใน C ++ 11 ลองนึกภาพว่าคุณกำลังทำงานกับ codebase แบบเดิมซึ่งต้องอาศัยไลบรารี C ++ 98 เป็นอย่างมากดังนั้นหากไม่มีทางเลือกในการกำหนดสำเนาการย้ายจะทำลายสิ่งต่างๆ


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