ขโมยทรัพยากรจาก std :: อนุญาตให้ใช้กุญแจของแผนที่ได้หรือไม่


15

ใน C ++ ตกลงเพื่อขโมยทรัพยากรจากแผนที่ที่ฉันไม่ต้องการอีกต่อไปหรือไม่? แม่นยำมากขึ้นถือว่าผมมีstd::mapกับstd::stringกุญแจและฉันต้องการที่จะสร้างเวกเตอร์ออกมาจากมันโดยการขโมยทรัพยากรของmapปุ่ม s std::moveใช้ โปรดทราบว่าการเข้าถึงเพื่อเขียนไปยังคีย์จะทำลายโครงสร้างข้อมูลภายใน (การเรียงลำดับคีย์) ของmapแต่ฉันจะไม่ใช้มันในภายหลัง

คำถาม : ฉันสามารถทำสิ่งนี้ได้โดยไม่มีปัญหาหรือสิ่งนี้จะนำไปสู่ข้อผิดพลาดที่ไม่คาดคิดเช่นในตัวทำลายของmapเพราะฉันเข้าถึงมันในแบบที่std::mapไม่ได้ตั้งใจหรือไม่?

นี่คือตัวอย่างโปรแกรม:

#include<map>
#include<string>
#include<vector>
#include<iostream>
using namespace std;
int main(int argc, char *argv[])
{
    std::vector<std::pair<std::string,double>> v;
    { // new scope to make clear that m is not needed 
      // after the resources were stolen
        std::map<std::string,double> m;
        m["aLongString"]=1.0;
        m["anotherLongString"]=2.0;
        //
        // now steal resources
        for (auto &p : m) {
            // according to my IDE, p has type 
            // std::pair<const class std::__cxx11::basic_string<char>, double>&
            cout<<"key before stealing: "<<p.first<<endl;
            v.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));
            cout<<"key after stealing: "<<p.first<<endl;
        }
    }
    // now use v
    return 0;
}

มันผลิตผลลัพธ์:

key before stealing: aLongString
key after stealing: 
key before stealing: anotherLongString
key after stealing: 

แก้ไข: ฉันต้องการทำสิ่งนี้กับเนื้อหาทั้งหมดของแผนที่ขนาดใหญ่และบันทึกการจัดสรรแบบไดนามิกโดยการขโมยทรัพยากรนี้


3
จุดประสงค์ของ "การขโมย" นี้คืออะไร? หากต้องการลบองค์ประกอบออกจากแผนที่ ถ้าเช่นนั้นทำไมไม่ทำอย่างนั้น (ลบองค์ประกอบออกจากแผนที่) นอกจากนี้การแก้ไขconstค่าจะเป็นUB เสมอ
โปรแกรมเมอร์บางคนเพื่อน

เห็นได้ชัดว่ามันจะนำไปสู่ข้อผิดพลาดร้ายแรง!
rezaebrh

1
ไม่ใช่คำตอบที่ตรงกับคำถามของคุณ แต่: จะเกิดอะไรขึ้นถ้าคุณไม่ส่งคืนเวกเตอร์ แต่เป็นช่วงหรือตัววนซ้ำคู่หนึ่ง ที่จะหลีกเลี่ยงการคัดลอกอย่างสมบูรณ์ ไม่ว่าในกรณีใดคุณต้องมีเกณฑ์มาตรฐานเพื่อติดตามความคืบหน้าของการปรับให้เหมาะสมและผู้สร้างโปรไฟล์เพื่อค้นหาฮอตสปอต
Ulrich Eckhardt

1
@ ALX23z คุณมีแหล่งข้อมูลใดสำหรับคำสั่งนี้ ฉันไม่สามารถจินตนาการได้ว่าการคัดลอกตัวชี้มีราคาแพงกว่าการคัดลอกพื้นที่ทั้งหมดของหน่วยความจำ
เซบาสเตียนฮอฟแมนน์

1
@SebastianHoffmann ถูกกล่าวถึงใน CppCon เมื่อเร็ว ๆ นี้ซึ่งไม่แน่ใจว่าจะพูดอะไร สิ่งนี้std::stringมีการเพิ่มประสิทธิภาพสั้น ๆ หมายความว่ามีตรรกะบางอย่างที่ไม่เกี่ยวกับการคัดลอกและการเคลื่อนย้ายและไม่ใช่แค่การแลกเปลี่ยนตัวชี้และนอกจากนี้เวลาส่วนใหญ่ยังหมายถึงการคัดลอก - เพื่อมิให้คุณจัดการกับสตริงที่ค่อนข้างยาว ความแตกต่างทางสถิติมีขนาดเล็กอย่างไรก็ตามโดยทั่วไปแล้วมันแตกต่างกันไปขึ้นอยู่กับชนิดของการประมวลผลสตริง
ALX23z

คำตอบ:


18

คุณกำลังทำพฤติกรรมที่ไม่ได้กำหนดใช้const_castเพื่อแก้ไขconstตัวแปร อย่าทำอย่างนั้น เหตุผลก็constเพราะแผนที่เรียงตามกุญแจ ดังนั้นการปรับเปลี่ยนกุญแจในสถานที่จึงเป็นการทำลายสมมติฐานที่สร้างขึ้นบนแผนที่

คุณไม่ควรใช้const_castเพื่อลบออกconstจากตัวแปรและปรับเปลี่ยนตัวแปรนั้น

ที่ถูกกล่าวว่า, C ++ 17 มีทางออกสำหรับปัญหาของคุณ: std::map's extractฟังก์ชั่น:

#include <map>
#include <string>
#include <vector>
#include <utility>

int main() {
  std::vector<std::pair<std::string, double>> v;
  std::map<std::string, double> m{{"aLongString", 1.0},
                                  {"anotherLongString", 2.0}};

  auto extracted_value = m.extract("aLongString");
  v.emplace_back(std::make_pair(std::move(extracted_value.key()),
                                std::move(extracted_value.mapped())));

  extracted_value = m.extract("anotherLongString");
  v.emplace_back(std::make_pair(std::move(extracted_value.key()),
                                std::move(extracted_value.mapped())));
}

using namespace std;และไม่ :)


ขอบคุณฉันจะลองสิ่งนี้! แต่คุณแน่ใจหรือว่าฉันทำไม่ได้เหมือนที่ฉันทำ? ฉันหมายถึงการที่mapจะไม่บ่นถ้าฉันไม่เรียกวิธีการของมัน (ซึ่งฉันไม่ได้ทำ) และบางทีการเรียงลำดับภายในนั้นไม่สำคัญในตัวทำลายระบบ
phinz

2
constคีย์ของแผนที่ที่ถูกสร้างขึ้น การกลายเป็นconstออบเจ็กต์คือ UB ทันทีไม่ว่าจะมีอะไรเข้าถึงได้ในภายหลังหรือไม่ก็ตาม
HTNW

วิธีนี้มีสองปัญหา: (1) ฉันต้องการแยกองค์ประกอบทั้งหมดฉันไม่ต้องการแยกตามคีย์ (การค้นหาที่ไม่มีประสิทธิภาพ) แต่โดยตัววนซ้ำ ฉันเห็นว่านี่เป็นไปได้เช่นกัน (2) ถูกต้องฉันถ้าฉันผิด แต่สำหรับการแยกองค์ประกอบทั้งหมดจะมีค่าใช้จ่ายมหาศาล (สำหรับการปรับสมดุลโครงสร้างต้นไม้ภายในทุก ๆ การสกัด)
phinz

2
@phinz อย่างที่คุณเห็นใน cppreference extractเมื่อใช้ตัววนซ้ำเนื่องจากอาร์กิวเมนต์มีการตัดจำหน่ายความซับซ้อนคงที่ ค่าใช้จ่ายบางอย่างไม่สามารถหลีกเลี่ยงได้ แต่อาจไม่สำคัญพอที่จะสำคัญ หากคุณมีข้อกำหนดพิเศษที่ไม่ครอบคลุมโดยสิ่งนี้คุณจะต้องปฏิบัติmapตามข้อกำหนดของคุณเอง stdภาชนะบรรจุที่มีความหมายสำหรับวัตถุประสงค์ทั่วไปที่พบบ่อย application.They จะไม่เหมาะสำหรับกรณีการใช้งานที่เฉพาะเจาะจง
walnut

@HTNW คุณแน่ใจหรือว่ากุญแจที่จะถูกสร้างขึ้นconst? ในกรณีนี้คุณสามารถโปรดระบุตำแหน่งที่การโต้แย้งของฉันผิด
phinz

4

รหัสของคุณพยายามที่จะแก้ไขconstวัตถุดังนั้นจึงมีพฤติกรรมที่ไม่ได้กำหนดไว้เนื่องจากคำตอบของ druckermanlyชี้ให้เห็นอย่างถูกต้อง

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

ความกังวลเกี่ยวกับ

มีความกังวลมากมายเกี่ยวกับการออกแบบนี้ เราจะอยู่ที่นี่

พฤติกรรมที่ไม่ได้กำหนด

ส่วนที่ยากที่สุดของข้อเสนอนี้จากมุมมองทางทฤษฎีคือความจริงที่ว่าองค์ประกอบที่แยกออกมานั้นยังคงประเภทคีย์ const ไว้ สิ่งนี้จะป้องกันไม่ให้เคลื่อนย้ายออกหรือเปลี่ยน เพื่อแก้ปัญหานี้ได้พิจารณาให้ความสำคัญฟังก์ชั่นการเข้าถึงซึ่งให้การเข้าถึงที่ไม่ const คีย์ในองค์ประกอบที่จัดขึ้นโดยจับโหนด ฟังก์ชั่นนี้ต้องการการนำไปใช้ "เวทย์มนตร์" เพื่อให้แน่ใจว่าทำงานได้อย่างถูกต้องเมื่อมีการเพิ่มประสิทธิภาพคอมไพเลอร์ วิธีหนึ่งที่จะทำเช่นนี้คือกับสหภาพของและpair<const key_type, mapped_type> pair<key_type, mapped_type>การแปลงระหว่างสิ่งเหล่านี้สามารถทำได้อย่างปลอดภัยโดยใช้เทคนิคที่คล้ายกับที่ใช้ std::launderในการสกัดและการแทรกซ้ำ

เราไม่รู้สึกว่าสิ่งนี้ก่อให้เกิดปัญหาทางเทคนิคหรือปรัชญาใด ๆ หนึ่งในเหตุผลที่ห้องสมุดมาตรฐานที่มีอยู่คือการเขียนโค้ดที่ไม่ใช่แบบพกพาและมีมนต์ขลังที่ลูกค้าไม่สามารถเขียนในภาษา C ++ แบบพกพา (เช่น<atomic>, <typeinfo>, <type_traits>ฯลฯ ) นี่เป็นเพียงตัวอย่างอีกตัวอย่างหนึ่ง สิ่งที่จำเป็นสำหรับผู้ขายคอมไพเลอร์เพื่อใช้เวทย์มนตร์นี้คือพวกเขาไม่ใช้พฤติกรรมที่ไม่ได้กำหนดในสหภาพเพื่อวัตถุประสงค์ในการปรับให้เหมาะสม - และในขณะนี้คอมไพเลอร์ก็สัญญากับสิ่งนี้ไว้แล้ว

นี้จะกำหนดข้อ จำกัด กับลูกค้าว่าถ้าฟังก์ชั่นเหล่านี้จะถูกนำมาใช้std::pairไม่สามารถความเชี่ยวชาญดังกล่าวที่มีรูปแบบที่แตกต่างกันมากกว่าpair<const key_type, mapped_type> pair<key_type, mapped_type>เรารู้สึกว่าโอกาสของทุกคนที่ต้องการทำสิ่งนี้เป็นศูนย์อย่างมีประสิทธิภาพและในถ้อยคำทางการเรา จำกัด ความเชี่ยวชาญของคู่เหล่านี้

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

(เน้นที่เหมือง)


นี่เป็นส่วนหนึ่งของคำตอบของคำถามเดิม แต่ฉันยอมรับได้เพียงข้อเดียว
phinz

0

ฉันไม่คิดอย่างนั้นconst_castและการแก้ไขนำไปสู่พฤติกรรมที่ไม่ได้กำหนดในกรณีนี้ แต่โปรดแสดงความคิดเห็นว่าการโต้แย้งนี้ถูกต้องหรือไม่

คำตอบนี้อ้างว่า

กล่าวอีกนัยหนึ่งคุณจะได้รับ UB ถ้าคุณปรับเปลี่ยนวัตถุ const ดั้งเดิมและไม่เช่นนั้น

ดังนั้นบรรทัดv.emplace_back(make_pair(std::move(const_cast<string&>(p.first)),p.second));ในคำถามจะไม่นำไปสู่ ​​UB หากว่าstringวัตถุp.firstนั้นไม่ได้ถูกสร้างเป็นวัตถุ const ตอนนี้ทราบว่าการ อ้างอิงเกี่ยวกับextractรัฐ

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

ดังนั้นถ้าฉันสอดคล้องกับ, ยังคงอาศัยอยู่ในสถานที่จัดเก็บ แต่หลังจากการสกัดผมได้รับอนุญาตให้ใช้ทรัพยากรต่าง ๆตามที่ระบุไว้ในคำตอบของนักเขียน ซึ่งหมายความว่าและด้วยเหตุนี้นอกจากนี้ยังมีวัตถุถูกไม่ได้สร้างเป็นวัตถุ const เดิมextractnode_handleppmoveppstringp.first

ดังนั้นผมคิดว่าการปรับเปลี่ยนของmap's คีย์ไม่นำไปสู่ UB และจากคำตอบ Deuchie ของมันก็ดูเหมือนว่ายังมีโครงสร้างที่เสียหายตอนนี้ (ตอนนี้หลายเดียวกันคีย์สตริงว่าง) ของmapไม่แนะนำปัญหาในเตาเผา ดังนั้นรหัสในคำถามควรทำงานได้ดีอย่างน้อยใน C ++ 17 ซึ่งมีextractวิธีการอยู่ (และคำสั่งเกี่ยวกับตัวชี้ที่เหลืออยู่ที่ถูกต้อง)

ปรับปรุง

ตอนนี้ฉันเห็นว่าคำตอบนี้ผิด ฉันไม่ได้ลบมันเพราะมันถูกอ้างอิงโดยคำตอบอื่น ๆ


1
ขออภัย phinz คำตอบของคุณไม่ถูกต้อง ฉันจะเขียนคำตอบอธิบายนี้ - std::launderมันมีสิ่งที่จะทำอย่างไรกับสหภาพแรงงานและ
LF

-1

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

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

อีกคำถามที่คุณโพสต์ว่าจะก่อให้เกิดปัญหาเมื่อทำการแยกแยะหรือไม่ไม่ใช่ปัญหา เมื่อแผนที่ deconstructs มันจะเรียก deconstructor ของแต่ละองค์ประกอบ (mapped_types ฯลฯ ) และmoveวิธีการทำให้แน่ใจว่ามันปลอดภัยที่จะแยกแยะชั้นเรียนหลังจากที่มันถูกย้าย ดังนั้นไม่ต้องกังวล โดยสรุปแล้วมันคือการดำเนินการmoveที่รับรองว่าจะปลอดภัยในการลบหรือกำหนดค่าใหม่บางอย่างให้กับคลาส "ย้าย" โดยเฉพาะสำหรับstringการmoveวิธีการอาจจะตั้งตัวชี้ถ่านในการnullptrดังนั้นจึงไม่ได้ลบข้อมูลจริงที่ถูกย้ายเมื่อ Deconstructor ของชั้นเดิมถูกเรียกว่า


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

ฉันยังทำการทดลองสองครั้งโดยใช้ MSVC 14.24.28314 และ Clang 9.0.0 ตามลำดับและพวกเขาให้ผลลัพธ์เหมือนกัน

map<string, int> m;
m.insert({ "test", 2 });
m.insert({ "this should be behind the 'test' string.", 3 });
m.insert({ "and this should be in front of the 'test' string.", 1 });
string let_me_USE_IT = std::move(const_cast<string&>(m.find("test")->first));
cout << let_me_USE_IT << '\n';
for (auto const& i : m) {
    cout << i.first << ' ' << i.second << '\n';
}

เอาท์พุท:

test
and this should be in front of the 'test' string. 1
 2
this should be behind the 'test' string. 3

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

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


2
"เป็นไปได้หรือไม่ว่าจะทำให้เกิดปัญหาเมื่อทำการแยกแยะสิ่งเหล่านั้นไม่ใช่ปัญหา" ถูกต้องทางเทคนิคเนื่องจากพฤติกรรมที่ไม่ได้กำหนด (การเปลี่ยนแปลงconstค่า) เกิดขึ้นก่อนหน้านี้ อย่างไรก็ตามอาร์กิวเมนต์ " move[ฟังก์ชั่น] ช่วยให้แน่ใจว่าปลอดภัยที่จะแยกแยะ [วัตถุของ] คลาสหลังจากที่ย้ายแล้ว" ไม่ถือ: คุณไม่สามารถย้ายจากconstวัตถุ / การอ้างอิงได้อย่างปลอดภัยเนื่องจากต้องมีการปรับเปลี่ยนซึ่งconstป้องกัน คุณสามารถลองแก้ไขข้อ จำกัด ดังกล่าวได้โดยการใช้const_castแต่ ณ จุดนั้นคุณจะพยายามปรับใช้พฤติกรรมที่เฉพาะเจาะจงอย่างลึกซึ้งหากไม่ใช่ UB
hoffmale

@hoffmale ขอบคุณฉันมองข้ามและทำผิดพลาดครั้งใหญ่ ถ้าไม่ใช่คุณที่ชี้ให้เห็นคำตอบของฉันที่นี่อาจทำให้คนอื่นเข้าใจผิด อันที่จริงผมควรจะพูดว่าmoveฟังก์ชั่นใช้&แทนดังนั้นหากหนึ่งยืนยันว่าเขาจะย้ายออกที่สำคัญของแผนที่เขาต้องใช้const& const_cast
Deuchie

1
"วัตถุที่ระบุว่า const ยังคงเป็นวัตถุเช่นเดียวกับที่ไม่ใช่กับ const ในแง่ของชนิดและการแทนในรูปแบบไบนารี" ไม่สามารถใช้วัตถุ const ในหน่วยความจำแบบอ่านอย่างเดียวเท่านั้น นอกจากนี้ const ช่วยให้คอมไพเลอร์ให้เหตุผลเกี่ยวกับรหัสและแคชค่าแทนที่จะสร้างรหัสสำหรับการอ่านหลาย ๆ ครั้ง (ซึ่งสามารถสร้างความแตกต่างอย่างมากในแง่ของประสิทธิภาพ) ดังนั้น UB ที่เกิดจากการconst_castเป็นไปอย่างน่ารังเกียจ อาจใช้เวลาส่วนใหญ่ แต่แบ่งรหัสของคุณในรูปแบบที่ลึกซึ้ง
LF

แต่ที่นี่ฉันคิดว่าเรามั่นใจได้ว่าวัตถุจะไม่ถูกใส่ในหน่วยความจำแบบอ่านอย่างเดียวเพราะหลังจากextractนั้นเราได้รับอนุญาตให้ย้ายจากวัตถุเดียวกันมากใช่ไหม (ดูคำตอบของฉัน)
phinz

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