ท่านจะไม่ได้รับมรดกจาก std :: vector


189

ตกลงนี้มันยากที่จะสารภาพ std::vectorแต่ฉันจะมีสิ่งล่อใจที่แข็งแกร่งในขณะนี้จะได้รับมรดกจาก

ฉันต้องการอัลกอริทึมแบบกำหนดเองประมาณ 10 แบบสำหรับเวกเตอร์และฉันต้องการให้พวกเขาเป็นสมาชิกของเวกเตอร์โดยตรง แต่โดยธรรมชาติฉันต้องการมีส่วนstd::vectorต่อประสานที่เหลือ ความคิดแรกของฉันในฐานะพลเมืองที่ปฏิบัติตามกฎหมายคือการมีstd::vectorสมาชิกในMyVectorชั้นเรียน แต่จากนั้นฉันจะต้องทบทวนส่วนติดต่อทั้งหมดของ std :: vector อีกครั้งด้วยตนเอง พิมพ์มากเกินไป ต่อไปฉันนึกถึงมรดกส่วนตัวดังนั้นแทนที่จะใช้วิธีประณามฉันจะเขียนบทความหลายusing std::vector::memberเรื่องในส่วนสาธารณะ อันนี้น่าเบื่อจริง ๆ แล้ว

และฉันอยู่ที่นี่ฉันคิดว่าฉันสามารถสืบทอดต่อสาธารณชนได้ง่ายstd::vectorๆ แต่ให้คำเตือนในเอกสารที่ว่าคลาสนี้ไม่ควรใช้ polymorphically ฉันคิดว่านักพัฒนาส่วนใหญ่มีความสามารถพอที่จะเข้าใจได้ว่าสิ่งนี้ไม่ควรใช้ polymorphically อยู่ดี

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

นอกจากนี้นอกเหนือจากความจริงที่ว่าคนงี่เง่าบางคนสามารถเขียนสิ่งที่ชอบ

std::vector<int>* p  = new MyVector

มีอันตรายอื่นที่เหมือนจริงในการใช้ MyVector หรือไม่? โดยบอกว่าสมจริงฉันจะละทิ้งสิ่งต่าง ๆ อย่างเช่นจินตนาการถึงฟังก์ชั่นที่ใช้ตัวชี้ไปยังเวกเตอร์ ...

ฉันได้กล่าวถึงกรณีของฉัน ฉันได้กระทำบาป. ตอนนี้ก็ขึ้นอยู่กับคุณที่จะให้อภัยฉันหรือไม่ :)


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

7
ทำไมคุณไม่ต้องการเพิ่มฟังก์ชันที่คุณต้องการด้วยฟังก์ชั่นที่ไม่ใช่สมาชิก? สำหรับฉันแล้วนั่นจะเป็นสิ่งที่ปลอดภัยที่สุดในสถานการณ์นี้
Simone

11
@Jim: std::vectorอินเทอร์เฟซของค่อนข้างใหญ่และเมื่อ C ++ 1x มาพร้อมมันจะขยายอย่างมาก นั่นเป็นประเภทที่มากและมีการขยายมากขึ้นในไม่กี่ปี ฉันคิดว่านี่เป็นเหตุผลที่ดีในการพิจารณาการสืบทอดแทนที่จะกักกัน - หากมีใครทำตามหลักฐานว่าหน้าที่เหล่านั้นควรเป็นสมาชิก (ซึ่งฉันสงสัย) กฎที่จะไม่ได้มาจากตู้สินค้า STL ก็คือพวกเขาไม่ได้เป็น polymorphic หากคุณไม่ได้ใช้วิธีดังกล่าวจะไม่สามารถใช้งานได้
sbi

9
เนื้อจริงของคำถามนั้นอยู่ในประโยคเดียว: "ฉันต้องการให้พวกเขาเป็นสมาชิกของเวกเตอร์โดยตรง" ไม่มีอะไรอื่นในคำถามที่สำคัญจริงๆ ทำไมคุณ "ต้องการ" สิ่งนี้? มีปัญหาอะไรกับการให้ฟังก์ชั่นนี้ในฐานะที่ไม่ใช่สมาชิก?
jalf

8
@ JoshC: "เจ้า" มักจะเป็นเรื่องธรรมดามากกว่า "เจ้าจะ" และมันก็เป็นรุ่นที่พบใน King James Bible (ซึ่งโดยทั่วไปคือสิ่งที่ผู้คนกำลังยิ่งทำให้เมื่อพวกเขาเขียน "เจ้าจะไม่ [... ] ") มีอะไรในโลกที่ทำให้คุณเรียกว่า "การสะกดคำผิด"
ruakh

คำตอบ:


155

ที่จริงแล้วไม่มีอะไรผิดปกติกับมรดกของ std::vectorที่จริงมีอะไรผิดปกติกับมรดกของประชาชนหากคุณต้องการสิ่งนี้เพียงทำเช่นนั้น

ฉันขอแนะนำให้ทำอย่างนั้นถ้าเป็นจริงเท่านั้นจำเป็นเท่านั้น เฉพาะในกรณีที่คุณไม่สามารถทำสิ่งที่คุณต้องการด้วยฟังก์ชั่นฟรีได้ (เช่นควรคงสถานะไว้)

ปัญหาคือว่าMyVectorเป็นนิติบุคคลใหม่ หมายความว่านักพัฒนา C ++ ใหม่ควรรู้ว่ามันคืออะไรก่อนที่จะใช้ ความแตกต่างระหว่างstd::vectorและMyVectorคืออะไร อันไหนดีกว่าที่จะใช้ที่นี่และที่นั่น? เกิดอะไรขึ้นถ้าฉันต้องการที่จะย้ายstd::vectorไปMyVector? ขอให้ฉันใช้swap()หรือไม่

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


7
สิ่งเดียวที่ฉันจะโต้แย้งได้คือต้องรู้ว่าเขากำลังทำอะไรอยู่ ยกตัวอย่างเช่นไม่แนะนำข้อมูลสมาชิกเพิ่มเติมลงMyVectorแล้วพยายามที่จะผ่านเข้ากับฟังก์ชั่นที่ยอมรับหรือstd::vector& std::vector*หากมีการมอบหมายการคัดลอกใด ๆ ที่เกี่ยวข้องโดยใช้ std :: vector * หรือ std :: vector & เรามีปัญหาการแบ่งส่วนที่สมาชิกข้อมูลใหม่ของMyVectorจะไม่ถูกคัดลอก สิ่งนี้จะเป็นจริงในการเรียกการแลกเปลี่ยนผ่านตัวชี้ฐาน / การอ้างอิง ฉันมักจะคิดว่าลำดับชั้นการสืบทอดใด ๆ ที่เสี่ยงต่อการแบ่งวัตถุเป็นสิ่งที่ไม่ดี
stinky472

13
std::vectordestructor ของไม่ใช่virtualดังนั้นคุณไม่ควรสืบทอดมัน
André Fratelli

2
ฉันสร้างคลาสที่ std :: vector สืบทอดต่อสาธารณะด้วยเหตุผลนี้: ฉันมีรหัสเก่าที่มีคลาสเวกเตอร์ที่ไม่ใช่ STL และฉันต้องการย้ายไปยัง STL ฉันปรับใช้คลาสเก่าเป็นคลาสที่ได้รับของ std :: vector อีกครั้งทำให้ฉันสามารถใช้ชื่อฟังก์ชันเก่า (เช่น Count () มากกว่า size ()) ในโค้ดเก่าได้ในขณะที่เขียนโค้ดใหม่โดยใช้ std :: vector ฟังก์ชั่น. ฉันไม่ได้เพิ่มสมาชิกข้อมูลใด ๆ ดังนั้น std :: vector destructor ทำงานได้ดีสำหรับวัตถุที่สร้างขึ้นบนฮีป
Graham Asher

3
@GrahamAsher หากคุณลบวัตถุผ่านตัวชี้ไปยังฐานและ destructor ไม่ใช่เสมือนโปรแกรมของคุณจะแสดงพฤติกรรมที่ไม่ได้กำหนด ผลลัพธ์หนึ่งที่เป็นไปได้ของพฤติกรรมที่ไม่ได้กำหนดคือ "มันใช้ได้ดีในการทดสอบของฉัน" อีกอย่างคืออีเมลของคุณยายของคุณประวัติการท่องเว็บ ทั้งสองสอดคล้องกับมาตรฐาน C ++ มันเปลี่ยนจากแบบหนึ่งไปอีกแบบหนึ่งด้วยการเปิดตัวคอมไพเลอร์ระบบปฏิบัติการหรือเฟสของดวงจันทร์ก็เป็นไปตาม
Yakk - Adam Nevraumont

2
@ GrahamAsher No เมื่อใดก็ตามที่คุณลบวัตถุใด ๆ ผ่านตัวชี้ไปยังฐานโดยไม่มีตัวทำลายเสมือนนั่นคือพฤติกรรมที่ไม่ได้กำหนดไว้ภายใต้มาตรฐาน ฉันเข้าใจสิ่งที่คุณคิดว่าเกิดขึ้น คุณเพิ่งจะผิด "ตัวทำลายคลาสพื้นฐานถูกเรียกใช้และทำงานได้" เป็นอาการหนึ่งที่เป็นไปได้ (และโดยทั่วไป) ของพฤติกรรมที่ไม่ได้กำหนดเนื่องจากเป็นรหัสเครื่องที่ไร้เดียงสาที่คอมไพเลอร์มักจะสร้าง สิ่งนี้ไม่ได้ทำให้ปลอดภัยหรือเป็นความคิดที่ดีที่จะทำ
Yakk - Adam Nevraumont

92

STL ทั้งหมดได้รับการออกแบบในลักษณะดังกล่าวว่าอัลกอริทึมและภาชนะบรรจุที่จะแยกจากกัน

สิ่งนี้นำไปสู่แนวคิดของตัววนซ้ำชนิดต่าง ๆ : ตัววนซ้ำตัววนซ้ำตัววนซ้ำแบบสุ่มเข้าถึงเป็นต้น

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

นอกจากนี้ให้ฉันเปลี่ยนเส้นทางคุณไปที่ บางคำพูดที่ดีโดยเจฟฟ์ Attwood


63

เหตุผลหลักสำหรับการไม่สืบทอดจากstd::vectorสาธารณะคือไม่มีตัวทำลายเสมือนที่ป้องกันคุณจากการใช้ polymorphic ของลูกหลานอย่างมีประสิทธิภาพ โดยเฉพาะอย่างยิ่งคุณจะไม่ได้รับอนุญาตจะdeletestd::vector<T>*นั้นจริง ๆ แล้วชี้ไปที่วัตถุที่ได้รับ (แม้ว่าคลาสที่ได้รับจะไม่มีสมาชิกเพิ่ม) แต่โดยทั่วไปคอมไพเลอร์ก็ไม่สามารถเตือนคุณได้

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

class AdVector: private std::vector<double>
{
    typedef double T;
    typedef std::vector<double> vector;
public:
    using vector::push_back;
    using vector::operator[];
    using vector::begin;
    using vector::end;
    AdVector operator*(const AdVector & ) const;
    AdVector operator+(const AdVector & ) const;
    AdVector();
    virtual ~AdVector();
};

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


IIUC กรณีที่ไม่มี destructor เสมือนเป็นเพียงปัญหาถ้าคลาสที่ได้รับจัดสรรทรัพยากรซึ่งจะต้องเป็นอิสระเมื่อถูกทำลาย (พวกเขาจะไม่ถูกปล่อยให้เป็นอิสระในกรณีการใช้ polymorphic เพราะบริบทโดยไม่รู้ตัวเป็นเจ้าของวัตถุที่ได้รับผ่านตัวชี้ไปยังฐานจะเรียก destructor ฐานเท่านั้นเมื่อถึงเวลา) ปัญหาที่คล้ายกันเกิดขึ้นจากฟังก์ชั่นสมาชิกที่ถูกแทนที่อื่น ๆ จะต้องดำเนินการว่าคนฐานที่ถูกต้องในการโทร แต่ไม่มีทรัพยากรเพิ่มเติมมีเหตุผลอื่นอีกไหม?
Peter - Reinstate Monica

2
vectorที่เก็บข้อมูลที่จัดสรรไว้ไม่ใช่ปัญหา แต่อย่างvectorใด destructor ของตัวเรียกนั้นจะถูกเรียกผ่านพvectorอยน์เตอร์ มันเป็นเพียงแค่ว่าห้ามมาตรฐานdeleteไอเอ็นจีจัดเก็บฟรีวัตถุผ่านการแสดงออกชั้นฐาน เหตุผลก็คือกลไกการจัดสรร (de) อาจพยายามอนุมานขนาดของก้อนหน่วยความจำให้เป็นอิสระจากdeleteตัวถูกดำเนินการตัวอย่างเช่นเมื่อมีการจัดสรรหลายจุดสำหรับวัตถุที่มีขนาดที่แน่นอน ข้อ จำกัด นี้จะไม่นำไปใช้กับการทำลายวัตถุตามปกติด้วยระยะเวลาการจัดเก็บแบบคงที่หรืออัตโนมัติ
ปีเตอร์ - Reinstate Monica

@DavisHerring ฉันคิดว่าเราเห็นด้วยที่นั่น :-)
Peter - Reinstate Monica

@DavisHerring อ่าฉันเข้าใจแล้วคุณอ้างถึงความคิดเห็นแรกของฉัน - มี IIUC ในความคิดเห็นนั้นและมันก็จบลงด้วยคำถาม ฉันเห็นในภายหลังว่ามันเป็นสิ่งต้องห้ามเสมอ (Basilevs ทำคำแถลงทั่วไป "ป้องกันอย่างมีประสิทธิภาพ" และฉันสงสัยเกี่ยวกับวิธีการเฉพาะที่ป้องกัน) ดังนั้นใช่เราเห็นด้วย: UB
Peter - Reinstate Monica

@Basilevs นั้นต้องมีการตั้งใจ แก้ไขแล้ว.
ThomasMcLeod

36

หากคุณกำลังพิจารณาเรื่องนี้แสดงว่าคุณได้ฆ่าคนใช้ภาษาในสำนักงานของคุณอย่างชัดเจนแล้ว กับพวกเขาออกไปทำไมไม่ทำแค่

struct MyVector
{
   std::vector<Thingy> v;  // public!
   void func1( ... ) ; // and so on
}

ที่จะหลีกเลี่ยงความผิดพลาดทั้งหมดที่เป็นไปได้ที่อาจจะออกมาจากการตั้งใจ upcasting ระดับ MyVector ของคุณและคุณยังสามารถเข้าถึงทุก Ops .vเวกเตอร์เพียงโดยการเพิ่มเล็ก ๆ น้อย ๆ


และการเปิดเผยคอนเทนเนอร์และอัลกอริทึม? ดูคำตอบของคอสด้านบน
bruno nery


19

คุณหวังจะทำอะไรให้สำเร็จ แค่ให้ฟังก์ชั่นบางอย่าง?

วิธีใช้สำนวน C ++ คือการเขียนฟังก์ชั่นฟรีที่ใช้ฟังก์ชั่น โอกาสที่คุณไม่จำเป็นต้องใช้ std :: vector โดยเฉพาะสำหรับฟังก์ชั่นที่คุณใช้งานซึ่งหมายความว่าคุณสูญเสียความสามารถในการนำมาใช้ซ้ำได้จริงโดยพยายามที่จะสืบทอดจาก std :: vector

ฉันขอแนะนำให้คุณดูห้องสมุดและส่วนหัวมาตรฐานและนั่งสมาธิว่าพวกเขาทำงานอย่างไร


5
ฉันไม่มั่นใจ คุณสามารถปรับปรุงด้วยรหัสที่เสนอบางส่วนเพื่ออธิบายว่าทำไม
Karl Knechtel

6
@ อาร์เมน: นอกจากความสวยงามแล้วมีเหตุผลอะไรที่ดีบ้างมั้ย
snemarch

12
@ อาร์เมน: ความสวยงามที่ดีกว่าและความมีน้ำใจที่ดียิ่งขึ้นก็คือการให้บริการฟรีfrontและbackฟังก์ชั่นด้วย :) (นอกจากนี้ยังพิจารณาตัวอย่างของฟรีbeginและendใน C ++ 0x และเพิ่ม.)
UncleBens

3
ฉันยังไม่ผิดปกติกับฟังก์ชั่นฟรี หากคุณไม่ชอบ "ความงาม" ของ STL อาจเป็นภาษาซีพลัสพลัสที่ผิดสำหรับคุณ และการเพิ่มฟังก์ชั่นของสมาชิกบางส่วนนั้นไม่สามารถแก้ไขได้
Frank Osterfeld

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

14

ฉันคิดว่ามีกฎน้อยมากที่ควรปฏิบัติตามอย่างเปิดเผย 100% ดูเหมือนคุณจะได้รับความคิดมากมายและเชื่อมั่นว่านี่เป็นวิธีที่จะไป ดังนั้น - ถ้ามีคนมาด้วยเหตุผลเฉพาะที่ดีไม่ควรทำเช่นนี้ - ฉันคิดว่าคุณควรทำตามแผนของคุณ


9
ประโยคแรกของคุณเป็นจริง 100% ของเวลา :)
Steve Fallows

5
น่าเสียดายที่ประโยคที่สองไม่ใช่ เขาไม่ได้คิดมาก คำถามส่วนใหญ่ไม่เกี่ยวข้อง ส่วนเดียวของมันที่แสดงถึงแรงจูงใจของเขาคือ "ฉันต้องการให้พวกเขาเป็นสมาชิกของเวกเตอร์โดยตรง" ฉันต้องการ. ไม่มีเหตุผลว่าทำไมสิ่งนี้จึงเป็นที่ต้องการ ซึ่งดูเหมือนว่าเขาไม่ได้คิดอะไรเลย
jalf

7

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

เหตุผลสำหรับตัวเลือกที่สองนั้นอาจเป็นเพียงอุดมการณ์เท่านั้นเพราะstd::vectors นั้นไม่ใช่ polymorphic และไม่มีความแตกต่างไม่ว่าคุณจะเปิดเผยstd::vectorส่วนติดต่อสาธารณะผ่านการรับมรดกสาธารณะหรือผ่านการเป็นสมาชิกสาธารณะ (สมมติว่าคุณจำเป็นต้องรักษาสถานะไว้ในวัตถุของคุณเพื่อที่คุณจะไม่สามารถออกไปพร้อมกับฟังก์ชั่นฟรี) ในบันทึกเสียงที่น้อยลงและจากมุมมองเชิงอุดมการณ์ดูเหมือนว่าstd::vectors เป็น "แนวคิดง่าย ๆ " ดังนั้นความซับซ้อนใด ๆ ในรูปแบบของวัตถุที่มีความเป็นไปได้ต่าง ๆ ในชั้นเรียนของพวกเขาอุดมการณ์ไม่ใช้ประโยชน์


คำตอบที่ดี ยินดีต้อนรับสู่ SO!
Armen Tsirunyan

4

ในแง่การปฏิบัติ: ถ้าคุณไม่มีสมาชิกข้อมูลใด ๆ ในคลาสที่ได้รับคุณไม่มีปัญหาใด ๆ แม้แต่ในการใช้ polymorphic คุณต้องการ destructor เสมือนจริงเท่านั้นถ้าขนาดของคลาสพื้นฐานและคลาสที่ได้รับนั้นแตกต่างกันและ / หรือคุณมีฟังก์ชันเสมือน (ซึ่งหมายถึง v-table)

แต่ในทางทฤษฎี: จาก [expr.delete] ใน C ++ 0x FCD: ในทางเลือกแรก (ลบวัตถุ) หากประเภทคงที่ของวัตถุที่จะลบแตกต่างจากประเภทแบบไดนามิกประเภทคงที่จะต้องเป็น คลาสพื้นฐานของชนิดไดนามิกของวัตถุที่จะลบและประเภทคงที่จะต้องมี destructor เสมือนจริงหรือพฤติกรรมที่ไม่ได้กำหนด

แต่คุณสามารถได้มาจาก std :: vector แบบส่วนตัวโดยไม่มีปัญหา ฉันใช้รูปแบบต่อไปนี้:

class PointVector : private std::vector<PointType>
{
    typedef std::vector<PointType> Vector;
    ...
    using Vector::at;
    using Vector::clear;
    using Vector::iterator;
    using Vector::const_iterator;
    using Vector::begin;
    using Vector::end;
    using Vector::cbegin;
    using Vector::cend;
    using Vector::crbegin;
    using Vector::crend;
    using Vector::empty;
    using Vector::size;
    using Vector::reserve;
    using Vector::operator[];
    using Vector::assign;
    using Vector::insert;
    using Vector::erase;
    using Vector::front;
    using Vector::back;
    using Vector::push_back;
    using Vector::pop_back;
    using Vector::resize;
    ...

3
"คุณต้องการ destructor เสมือนจริงเท่านั้นถ้าขนาดของคลาสพื้นฐานและคลาสที่ได้รับนั้นแตกต่างกันไป / หรือคุณมีฟังก์ชั่นเสมือน (ซึ่งหมายถึง v-table)" การอ้างสิทธิ์นี้ถูกต้องจริง แต่ไม่ใช่ในทางทฤษฎี
Armen Tsirunyan

2
ใช่ในหลักการมันยังคงเป็นพฤติกรรมที่ไม่ได้กำหนด
jalf

หากคุณอ้างว่านี่เป็นพฤติกรรมที่ไม่ได้กำหนดฉันต้องการดูหลักฐาน (ใบเสนอราคาจากมาตรฐาน)
hmuelner

8
@hmuelner: น่าเสียดายที่ Armen และ jalf นั้นถูกต้อง จาก[expr.delete]ใน C ++ 0x FCD: <quote> ในอีกทางเลือกแรก (ลบวัตถุ), ถ้าวัตถุคงที่ที่จะลบแตกต่างจากประเภทแบบไดนามิกประเภทคงที่จะเป็นชั้นฐานของประเภทแบบไดนามิก ของวัตถุที่จะลบและประเภทคงที่จะมี destructor เสมือนจริงหรือพฤติกรรมไม่ได้กำหนด </quote>
Ben Voigt

1
ซึ่งเป็นเรื่องตลกเพราะจริง ๆ แล้วฉันคิดว่าพฤติกรรมนั้นขึ้นอยู่กับการปรากฏตัวของ destructor ที่ไม่น่ารำคาญ
Ben Voigt

3

ถ้าคุณทำตามสไตล์ C ++ ที่ดีการขาดฟังก์ชั่นเสมือนนั้นไม่ใช่ปัญหา แต่เป็นการแบ่งส่วน (ดู https://stackoverflow.com/a/14461532/877329 )

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

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    delete obj;
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj); //Will crash here. But caller does not know that
//  ...
    }

ในทางตรงกันข้ามสิ่งนี้จะใช้งานได้เสมอ (โดยมีหรือไม่มีตัวทำลายเสมือน):

void foo(SomeType* obj)
    {
    if(obj!=nullptr) //The function prototype only makes sense if parameter is optional
        {
        obj->doStuff();
        }
    }

class SpecialSomeType:public SomeType
    {
    // whatever 
    };

int main()
    {
    SpecialSomeType obj;
    doStuff(&obj);
//  The correct destructor *will* be called here.
    }

หากวัตถุถูกสร้างขึ้นโดยโรงงานโรงงานก็ควรกลับตัวชี้ไปยังตัวคั่นการทำงานซึ่งควรใช้แทนdeleteเนื่องจากโรงงานอาจใช้ฮีปของตัวเอง โทรจะได้รับมันในรูปแบบของ หรือshare_ptr unique_ptrในระยะสั้นไม่ได้มีdeleteอะไรที่คุณไม่ได้รับโดยตรงnewจาก


2

ใช่มันปลอดภัยตราบใดที่คุณระวังอย่าทำสิ่งที่ไม่ปลอดภัย ... ฉันไม่คิดว่าฉันเคยเห็นใครใช้เวกเตอร์ด้วยของใหม่ดังนั้นในทางปฏิบัติคุณอาจจะสบายดี อย่างไรก็ตามมันไม่ใช่สำนวนทั่วไปใน c ++ ....

คุณสามารถให้ข้อมูลเพิ่มเติมเกี่ยวกับอัลกอริทึมคืออะไร?

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

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


2

ฉันยังสืบทอดมาจากstd::vectorเมื่อเร็ว ๆ นี้และพบว่ามันมีประโยชน์มากและจนถึงตอนนี้ฉันยังไม่พบปัญหาใด ๆ

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

ปัญหาเดียวที่ฉันสามารถมองเห็นได้ด้วยวิธีการที่เป็นไม่มากกับ destructor ไม่ใช่เสมือน แต่บางวิธีการอื่น ๆ ซึ่งผมอยากจะเกินเช่นpush_back(), resize(),insert()ฯลฯ มรดกเอกชนสามารถจริงจะเป็นตัวเลือกที่ดี

ขอบคุณ!


10
จากประสบการณ์ของผมความเสียหายที่เลวร้ายที่สุดในระยะยาวมักจะเกิดจากคนที่พยายามบางสิ่งบางอย่างไม่รอบคอบและ " เพื่อให้ห่างไกลไม่ได้มีประสบการณ์ (อ่านสังเกต ) ปัญหาใด ๆ กับมัน"
ไม่แยแส

0

ที่นี่ฉันขอแนะนำอีก 2 วิธีที่คุณต้องการ หนึ่งคืออีกหนึ่งวิธีในการพันอีกวิธีstd::vectorหนึ่งคือวิธีสืบทอดโดยไม่ให้ผู้ใช้มีโอกาสทำลายอะไร:

  1. ให้ฉันเพิ่มอีกวิธีในการพันstd::vectorโดยไม่ต้องเขียนตัวห่อฟังก์ชั่นมากมาย

#include <utility> // For std:: forward
struct Derived: protected std::vector<T> {
    // Anything...
    using underlying_t = std::vector<T>;

    auto* get_underlying() noexcept
    {
        return static_cast<underlying_t*>(this);
    }
    auto* get_underlying() const noexcept
    {
        return static_cast<underlying_t*>(this);
    }

    template <class Ret, class ...Args>
    auto apply_to_underlying_class(Ret (*underlying_t::member_f)(Args...), Args &&...args)
    {
        return (get_underlying()->*member_f)(std::forward<Args>(args)...);
    }
};
  1. การสืบทอดจากstd :: spanแทนstd::vectorและหลีกเลี่ยงปัญหา dtor

0

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

คุณจำเป็นต้องใช้ความระมัดระวังตามปกติทุกชนิดที่ได้รับมา แต่ไม่มีอะไรพิเศษเกี่ยวกับกรณีของฐานจาก Standard การเอาชนะฟังก์ชั่นสมาชิกพื้นฐานอาจเป็นเรื่องยุ่งยาก แต่ก็ไม่ฉลาดเลยที่จะทำกับฐานที่ไม่ใช่แบบเสมือนดังนั้นจึงไม่มีอะไรพิเศษที่นี่ หากคุณต้องการเพิ่มข้อมูลสมาชิกคุณจะต้องกังวลเกี่ยวกับการแบ่งส่วนหากสมาชิกต้องรักษาความสอดคล้องกับเนื้อหาของฐาน แต่อีกครั้งนั้นจะเหมือนกันสำหรับฐานใด ๆ

สถานที่ที่ฉันพบว่ามาจากคอนเทนเนอร์มาตรฐานที่มีประโยชน์เป็นพิเศษคือการเพิ่มคอนสตรัคเตอร์เดียวที่สามารถกำหนดค่าเริ่มต้นได้อย่างแม่นยำโดยไม่ต้องมีความสับสนหรือการไฮแจ็กโดยผู้สร้างรายอื่น (ฉันกำลังมองหาคุณ, initialization_list constructors!) จากนั้นคุณสามารถใช้วัตถุที่เป็นผลลัพธ์โดยแบ่งออกเป็นชิ้น ๆ - ส่งมันโดยอ้างอิงถึงสิ่งที่คาดหวังจากฐานย้ายจากสิ่งนั้นไปเป็นตัวอย่างของฐานคุณมีอะไรบ้าง ไม่มีกรณีขอบที่ต้องกังวลเว้นแต่จะรบกวนคุณผูกอาร์กิวเมนต์แม่แบบกับคลาสที่ได้รับ

สถานที่ที่เทคนิคนี้จะมีประโยชน์ทันทีใน C ++ 20 คือการจอง สถานที่ที่เราอาจจะเขียน

  std::vector<T> names; names.reserve(1000);

เราสามารถพูดได้

  template<typename C> 
  struct reserve_in : C { 
    reserve_in(std::size_t n) { this->reserve(n); }
  };

แล้วมีแม้ในขณะที่สมาชิกชั้นเรียน

  . . .
  reserve_in<std::vector<T>> taken_names{1000};  // 1
  std::vector<T> given_names{reserve_in<std::vector<T>>{1000}}; // 2
  . . .

(ตามความต้องการ) และไม่จำเป็นต้องเขียนนวกรรมิกเพียงเพื่อโทรสำรอง () กับพวกเขา

เหตุผลที่ reserve_inทางเทคนิคที่ต้องรอ C ++ 20 ก็คือมาตรฐานก่อนหน้านี้ไม่ต้องการความสามารถของเวกเตอร์เปล่าที่จะถูกเก็บไว้ข้ามการเคลื่อนไหวซึ่งได้รับการยอมรับว่าเป็นการกำกับดูแลและคาดว่าจะได้รับการแก้ไขอย่างสมเหตุสมผล เป็นข้อบกพร่องในเวลาสำหรับ '20 นอกจากนี้เรายังสามารถคาดหวังว่าการแก้ไขจะเป็นไปอย่างมีประสิทธิภาพย้อนหลังไปยังมาตรฐานก่อนหน้านี้เพราะการใช้งานที่มีอยู่ทั้งหมดจะรักษาความสามารถในการเคลื่อนไหวข้ามมาตรฐานไม่เพียงต้องการ ปืน - การจองเป็นเพียงการเพิ่มประสิทธิภาพเสมอ)

บางคนอาจโต้แย้งว่ากรณีของการreserve_inให้บริการที่ดีขึ้นโดยแม่แบบฟังก์ชั่นฟรี:

  template<typename C> 
  auto reserve_in(std::size_t n) { C c; c.reserve(n); return c; }

ทางเลือกดังกล่าวสามารถใช้งานได้อย่างแน่นอน - และในบางครั้งอาจเร็วกว่าเดิมเล็กน้อยเนื่องจาก * RVO แต่ทางเลือกของการสืบทอดมาหรือฟังก์ชั่นฟรีควรทำด้วยตัวเองและไม่ใช่จากความเชื่อโชคลางที่ไม่มีมูลความจริง (heh!) เกี่ยวกับการได้มาจากส่วนประกอบมาตรฐาน ในตัวอย่างการใช้งานด้านบนเฉพาะรูปแบบที่สองเท่านั้นที่จะทำงานกับฟังก์ชันฟรีได้ แม้ว่าจะอยู่นอกบริบทของชั้นเรียน แต่ก็สามารถเขียนได้กระชับกว่านี้เล็กน้อย:

  auto given_names{reserve_in<std::vector<T>>(1000)}; // 2
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.