คุณสมบัติที่ซ่อนอยู่ของ C ++? [ปิด]


114

ไม่ชอบ C ++ เมื่อพูดถึงบรรทัดคำถาม "คุณลักษณะที่ซ่อนอยู่" หรือไม่? คิดว่าฉันจะโยนมันออกไปที่นั่น อะไรคือคุณสมบัติที่ซ่อนอยู่ของ C ++?


@Devtron - ฉันเคยเห็นจุดบกพร่องบางอย่าง (เช่นพฤติกรรมที่ไม่คาดคิด) ขายเป็นคุณสมบัติ ในความเป็นจริงอุตสาหกรรมเกมพยายามที่จะทำให้สิ่งนี้เกิดขึ้นในปัจจุบันและเรียกมันว่า "เกมเพลย์ที่เกิดขึ้น" (ลองดู "TK Surfing" จาก Psi-Ops เป็นจุดบกพร่องอย่างแท้จริงจากนั้นพวกเขาก็ปล่อยให้มันเป็นเช่นนั้นและเป็นหนึ่งใน คุณสมบัติที่ดีที่สุดของเกม IMHO)
Grant Peters

5
@Laith J: ไม่ค่อยมีใครอ่านมาตรฐาน ISO C ++ 786 หน้าจากหน้าปก - แต่ฉันคิดว่าคุณมีและคุณได้เก็บมันไว้ทั้งหมดใช่มั้ย?
j_random_hacker

2
@Laith, @j_random: ดูคำถามของฉัน "คืออะไรเป็นเรื่องตลกของโปรแกรมเมอร์ฉันจะรู้จักมันและสิ่งที่เป็นคำตอบที่เหมาะสม" ที่stackoverflow.com/questions/1/you-have-been-link-rolled

ดูmeta.stackexchange.com/questions/56669/… , meta.stackexchange.com/questions/57226/…และโพสต์ Meta ที่เกี่ยวข้อง

คำตอบ:


308

โปรแกรมเมอร์ C ++ ส่วนใหญ่คุ้นเคยกับตัวดำเนินการ ternary:

x = (y < 0) ? 10 : 20;

อย่างไรก็ตามพวกเขาไม่ทราบว่าสามารถใช้เป็น lvalue ได้:

(a == 0 ? a : b) = 1;

ซึ่งเป็นชวเลขสำหรับ

if (a == 0)
    a = 1;
else
    b = 1;

ใช้ด้วยความระมัดระวัง :-)


11
น่าสนใจมาก. ฉันเห็นว่ามีการสร้างโค้ดที่อ่านไม่ได้
Jason Baker

112
Yikes (ก == 0? a: b) = (y <0? 10: 20);
Jasper Bekkers

52
(b? trueCount: falseCount) ++
Pavel Radzivilovsky

12
Dunno ถ้ามันเฉพาะของ GCC (value ? function1 : function2)()แต่ฉันรู้สึกประหลาดใจที่จะพบนี้ยังทำงาน:
Chris Burt-Brown

3
@Chris Burt-Brown: ไม่ควรใช้งานได้ทุกที่หากมีประเภทเดียวกัน (เช่นไม่มีอาร์กิวเมนต์ผิดนัด) function1และfunction2ถูกแปลงเป็นตัวชี้ฟังก์ชันโดยนัยและผลลัพธ์จะถูกแปลงกลับโดยปริยาย
MSalters

238

คุณสามารถใส่ URI ลงในซอร์ส C ++ ได้โดยไม่มีข้อผิดพลาด ตัวอย่างเช่น:

void foo() {
    http://stackoverflow.com/
    int bar = 4;

    ...
}

41
แต่มีเพียงหนึ่งฟังก์ชันเท่านั้นฉันสงสัย? :)
คอนสแตนติน

51
@jpoh: http ตามด้วยเครื่องหมายจุดคู่จะกลายเป็น "ป้ายกำกับ" ที่คุณใช้ในคำสั่ง goto ในภายหลัง คุณได้รับคำเตือนจากคอมไพเลอร์ของคุณเนื่องจากไม่ได้ใช้ในคำสั่ง goto ใด ๆ ในตัวอย่างข้างต้น
utku_karatas

9
คุณสามารถเพิ่มได้มากกว่าหนึ่งอย่างตราบเท่าที่มีโปรโตคอลที่แตกต่างกัน! ftp.microsoft.com gopher: //aerv.nl/1 และอื่น ๆ ...
Daniel Earwicker

4
@Pavel: ตัวระบุตามด้วยเครื่องหมายจุดคู่คือป้ายกำกับ (สำหรับใช้กับgotoC ++ ซึ่งมี) สิ่งที่ตามหลังเครื่องหมายทับสองตัวคือความคิดเห็น จึงมีhttp://stackoverflow.com, httpเป็นฉลาก (คุณในทางทฤษฎีสามารถเขียนgoto http;) และ//stackoverflow.comเป็นเพียงความคิดเห็นที่สิ้นสุดของเส้น ทั้งสองอย่างนี้เป็น C ++ ที่ถูกกฎหมายดังนั้นโครงสร้างจึงคอมไพล์ แน่นอนว่ามันไม่ได้ทำประโยชน์อะไรอย่างคลุมเครือ
David Thornley

8
ขออภัยgoto http;ไม่ได้ติดตาม URL จริงๆ :(
Yakov Galka

140

ตัวชี้เลขคณิต

โปรแกรมเมอร์ C ++ ชอบที่จะหลีกเลี่ยงพอยน์เตอร์เนื่องจากมีบั๊กที่สามารถแนะนำได้

C ++ ที่เจ๋งที่สุดที่ฉันเคยเห็นมา? ตัวอักษรอนาล็อก


11
เราหลีกเลี่ยงตัวชี้เนื่องจากข้อบกพร่อง? ตัวชี้เป็นทุกอย่างที่เกี่ยวกับการเข้ารหัส C ++ แบบไดนามิก!
Nick Bedford

1
ตัวอักษรแบบอะนาล็อกเหมาะสำหรับรายการแข่งขัน C ++ ที่สับสนโดยเฉพาะประเภท ASCII-art
Synetech

119

ฉันเห็นด้วยกับโพสต์ส่วนใหญ่ที่นั่น: C ++ เป็นภาษาที่มีหลายกระบวนทัศน์ดังนั้นคุณลักษณะ "ที่ซ่อนอยู่" ที่คุณจะพบ (นอกเหนือจาก "พฤติกรรมที่ไม่ได้กำหนด" ซึ่งคุณควรหลีกเลี่ยงโดยเสียค่าใช้จ่ายทั้งหมด) เป็นการใช้สิ่งอำนวยความสะดวกอย่างชาญฉลาด

สิ่งอำนวยความสะดวกเหล่านี้ส่วนใหญ่ไม่ใช่คุณลักษณะที่สร้างขึ้นในภาษา แต่เป็นสิ่งที่ใช้ห้องสมุด

ที่สำคัญที่สุดคือRAIIซึ่งมักจะถูกละเลยมาหลายปีโดยนักพัฒนา C ++ ที่มาจากโลก C การโอเวอร์โหลดของตัวดำเนินการมักเป็นคุณลักษณะที่เข้าใจผิดซึ่งเปิดใช้งานทั้งพฤติกรรมที่เหมือนอาร์เรย์ (ตัวดำเนินการตัวห้อย) ตัวชี้เหมือนการดำเนินการ (ตัวชี้อัจฉริยะ) และการดำเนินการในลักษณะที่สร้างขึ้น (เมทริกซ์การคูณ

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

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

การใช้ประโยชน์อื่น ๆ ของกระบวนทัศน์หลายประการในการสร้าง "วิธีการเขียนโปรแกรม" นอกบรรพบุรุษของ C ++ นั่นคือ C

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

ด้วยการใช้เทมเพลตคุณสามารถสร้างโค้ดที่ใช้ได้กับเกือบทุกประเภทรวมถึงไม่ใช่แบบที่คุณคิดไว้ในตอนแรก คุณสามารถเพิ่มความปลอดภัยของประเภทได้เช่นกัน (เช่น typesafe อัตโนมัติ malloc / realloc / ฟรี) คุณสมบัติของวัตถุ C ++ มีประสิทธิภาพมาก (ดังนั้นจึงเป็นอันตรายหากใช้อย่างไม่ระมัดระวัง) แต่แม้กระทั่งความหลากหลายแบบไดนามิกก็มีเวอร์ชันคงที่ใน C ++: CRTP CRTP

ฉันพบว่าหนังสือประเภท " C ++ ที่มีประสิทธิภาพ " ส่วนใหญ่จาก Scott Meyers หรือหนังสือประเภท " C ++ พิเศษ " จาก Herb Sutter นั้นอ่านง่ายและมีข้อมูลมากมายเกี่ยวกับคุณลักษณะที่เป็นที่รู้จักและไม่ค่อยมีคนรู้จักของ C ++

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

  • ใน C ++ อินเทอร์เฟซของคลาสเป็นทั้งฟังก์ชันสมาชิกและฟังก์ชันที่ไม่ใช่สมาชิกในเนมสเปซเดียวกัน

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

สิ่งนี้ไม่เคยสร้างความประหลาดใจแม้แต่นักพัฒนาที่มีประสบการณ์

(ที่มา: Guru ออนไลน์ของ Herb Sutter ประจำสัปดาห์ # 84: http://www.gotw.ca/gotw/084.htm )


+1 คำตอบที่ละเอียดมาก มันไม่สมบูรณ์ด้วยเหตุผลที่ชัดเจน (มิฉะนั้นจะไม่มี "คุณสมบัติที่ซ่อนอยู่" อีกต่อไป!): p ในจุดแรกท้ายคำตอบคุณได้กล่าวถึงสมาชิกของอินเทอร์เฟซคลาส คุณหมายถึง ".. เป็นทั้งฟังก์ชันสมาชิกและฟังก์ชันที่ไม่ใช่สมาชิกของเพื่อน " หรือไม่?
wilhelmtell


สิ่งที่คุณกำลังพูดถึงเกี่ยวกับ 1 ต้องเป็น koenig lookup ใช่มั้ย?
Özgür

1
@wilhelmtell: ไม่ไม่ไม่ ... :-p ... ฉันหมายถึง "ฟังก์ชันที่เป็นสมาชิกและฟังก์ชันที่ไม่ใช่สมาชิกที่ไม่ใช่เพื่อน" .... การค้นหาของ Koenig จะทำให้แน่ใจว่าฟังก์ชันเหล่านี้จะได้รับการพิจารณาเร็วกว่าฟังก์ชันอื่น ๆ " นอก "ในการค้นหาสัญลักษณ์
paercebal

7
โพสต์ที่ยอดเยี่ยมและ +1 โดยเฉพาะในส่วนสุดท้ายซึ่งมีคนจำนวนน้อยเกินไปที่จะรู้ ฉันอาจจะเพิ่มไลบรารี Boost เป็น "คุณลักษณะที่ซ่อนอยู่" ด้วย ฉันคิดว่ามันเป็นไลบรารีมาตรฐานที่ C ++ ควรมี ;)
jalf

118

คุณลักษณะภาษาหนึ่งที่ฉันคิดว่าค่อนข้างซ่อนเร้นเพราะฉันไม่เคยได้ยินเกี่ยวกับเรื่องนี้มาตลอดช่วงเวลาที่เรียนในโรงเรียนคือนามแฝงเนมสเปซ ฉันไม่ได้รับความสนใจจนกว่าฉันจะพบตัวอย่างของมันในเอกสารการเพิ่มประสิทธิภาพ แน่นอนตอนนี้ฉันรู้แล้วคุณสามารถค้นหาได้ในการอ้างอิง C ++ มาตรฐานใด ๆ

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );

1
usingผมคิดว่านี้จะเป็นประโยชน์ถ้าคุณไม่ต้องการที่จะใช้
Siqi Lin

4
นอกจากนี้ยังมีประโยชน์ในการใช้เป็นวิธีสลับระหว่างการใช้งานไม่ว่าจะเป็นการเลือกบอกว่าเธรดปลอดภัยกับไม่เธรดปลอดภัยหรือเวอร์ชัน 1 กับ 2
Tony Delroy

3
จะมีประโยชน์อย่างยิ่งหากคุณกำลังทำงานในโปรเจ็กต์ขนาดใหญ่ที่มีลำดับชั้นของเนมสเปซขนาดใหญ่และคุณไม่ต้องการให้ส่วนหัวของคุณก่อให้เกิดมลพิษเนมสเปซ (และคุณต้องการให้การประกาศตัวแปรของคุณสามารถอ่านได้โดยมนุษย์)
Brandon Bohrer

102

ไม่เพียง แต่สามารถประกาศตัวแปรในส่วนเริ่มต้นของforลูปเท่านั้น แต่ยังรวมถึงคลาสและฟังก์ชันด้วย

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
    ...
}

ที่อนุญาตให้มีตัวแปรหลายประเภทที่แตกต่างกัน


31
ยินดีที่ได้รู้ว่าคุณทำได้ แต่โดยส่วนตัวแล้วฉันพยายามหลีกเลี่ยงการทำอะไรแบบนั้นจริงๆ ส่วนใหญ่เป็นเพราะอ่านยาก
Zoomulator

2
จริงๆแล้วสิ่งที่จะใช้ได้ในบริบทนี้คือการใช้คู่: for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz

2
@ วาเลนตินฉันขอแนะนำให้คุณลองทำรายงานข้อผิดพลาดกับ VS2008 แทนการลงคะแนนคุณสมบัติที่ซ่อนอยู่ เห็นได้ชัดว่าเป็นความผิดของคอมไพเลอร์ของคุณ
Johannes Schaub - litb

2
อืมมันใช้ไม่ได้กับ msvc10 เหมือนกัน เศร้าแค่ไหน :(
avakar

2
ในความเป็นจริง @avakar gcc แนะนำข้อผิดพลาดที่ทำให้ปฏิเสธเช่นกันใน v4.6 :) ดูgcc.gnu.org/bugzilla/show_bug.cgi?id=46791
Johannes Schaub - litb

77

ตัวดำเนินการอาร์เรย์เชื่อมโยงกัน

A [8] เป็นคำพ้องความหมายของ * (A + 8) เนื่องจากการบวกเป็นความเชื่อมโยงจึงสามารถเขียนใหม่เป็น * (8 + A) ซึ่งเป็นคำพ้องความหมายของ ..... 8 [A]

คุณไม่ได้บอกว่ามีประโยชน์ ... :-)


15
จริงๆแล้วเมื่อใช้เคล็ดลับนี้คุณควรใส่ใจกับประเภทที่คุณใช้ A [8] คือ A ที่ 8 ในขณะที่ 8 [A] คือจำนวนเต็ม Ath เริ่มต้นที่แอดเดรส 8 ถ้า A เป็นไบต์แสดงว่าคุณมีบั๊ก
Vincent Robert

38
คุณหมายถึง "commutative" ที่คุณพูดว่า "Associative"?
DarenW

28
Vincent คุณคิดผิด ประเภทAไม่สำคัญเลย ตัวอย่างเช่นหากAเป็น a char*รหัสจะยังคงใช้ได้
Konrad Rudolph

11
ระวังว่า A ต้องเป็นตัวชี้และไม่ใช่ตัวดำเนินการที่โอเวอร์โหลดคลาส []
David Rodríguez - น้ำลายไหล

15
Vincent ในสิ่งนี้จะต้องมีหนึ่งประเภทหนึ่งและประเภทตัวชี้หนึ่งตัวและทั้ง C และ C ++ ไม่สนใจว่าอันไหนไปก่อน
David Thornley

73

สิ่งหนึ่งที่ไม่ค่อยมีใครรู้ก็คือสหภาพแรงงานสามารถเป็นแม่แบบได้เช่นกัน:

template<typename From, typename To>
union union_cast {
    From from;
    To   to;

    union_cast(From from)
        :from(from) { }

    To getTo() const { return to; }
};

และสามารถมีตัวสร้างและฟังก์ชันสมาชิกได้ด้วย ไม่มีอะไรเกี่ยวข้องกับการสืบทอด (รวมถึงฟังก์ชันเสมือน)


! ที่น่าสนใจ ดังนั้นคุณต้องเริ่มต้นสมาชิกทั้งหมดหรือไม่? เป็นไปตามลำดับโครงสร้างปกติซึ่งหมายความว่าสมาชิกคนสุดท้ายจะถูกเริ่มต้น "ด้านบน" สมาชิกก่อนหน้านี้หรือไม่
j_random_hacker

j_random_hacker โอ้ใช่เรื่องไร้สาระ จับดี. ฉันเขียนมันเพราะมันจะเป็นโครงสร้าง รอฉันจะแก้ไข
Johannes Schaub - litb

สิ่งนี้ไม่ก่อให้เกิดพฤติกรรมที่ไม่ได้กำหนดหรือไม่?
Greg Bacon

7
@gbacon ใช่มันไม่วิงวอนพฤติกรรมที่ไม่ได้กำหนดถ้าFromและToมีการตั้งค่าและใช้งานตามความเหมาะสม การรวมกันดังกล่าวสามารถใช้กับพฤติกรรมที่กำหนดได้แม้ว่า (โดยToเป็นอาร์เรย์ของถ่านที่ไม่ได้ลงนามหรือโครงสร้างที่ใช้ลำดับเริ่มต้นร่วมFromกัน แม้ว่าคุณจะใช้ในลักษณะที่ไม่ได้กำหนด แต่ก็ยังอาจมีประโยชน์สำหรับงานระดับต่ำ อย่างไรก็ตามนี่เป็นเพียงตัวอย่างหนึ่งของเทมเพลตยูเนี่ยน - อาจมีการใช้งานอื่น ๆ สำหรับสหภาพเทมเพลต
Johannes Schaub - litb

3
ระมัดระวังกับผู้สร้าง โปรดทราบว่าคุณจำเป็นต้องสร้างองค์ประกอบแรกเท่านั้นและอนุญาตเฉพาะใน C ++ 0x ตามมาตรฐานปัจจุบันคุณต้องยึดติดกับประเภทที่สร้างได้เล็กน้อย และไม่มีผู้ทำลาย
Potatoswatter

72

C ++ เป็นมาตรฐานไม่ควรมีคุณสมบัติแอบแฝง ...

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


65

คุณสมบัติที่ซ่อนอยู่อีกอย่างที่ใช้ไม่ได้ใน C คือการทำงานของยูนารี +การยูคุณสามารถใช้เพื่อส่งเสริมและสลายสิ่งต่างๆได้ทุกประเภท

การแปลงการแจงนับเป็นจำนวนเต็ม

+AnEnumeratorValue

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

รับค่าจากตัวแปร

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

struct Foo {
  static int const value = 42;
};

// This does something interesting...
template<typename T>
void f(T const&);

int main() {
  // fails to link - tries to get the address of "Foo::value"!
  f(Foo::value);

  // works - pass a temporary value
  f(+Foo::value);
}

สลายอาร์เรย์เป็นตัวชี้

คุณต้องการส่งพอยน์เตอร์สองตัวไปยังฟังก์ชัน แต่ใช้งานไม่ได้หรือไม่ เจ้าหน้าที่อาจช่วยได้

// This does something interesting...
template<typename T>
void f(T const& a, T const& b);

int main() {
  int a[2];
  int b[3];
  f(a, b); // won't work! different values for "T"!
  f(+a, +b); // works! T is "int*" both time
}

61

อายุการใช้งานของ temporaries ที่เชื่อมโยงกับการอ้างอิง const เป็นสิ่งที่คนไม่กี่คนรู้ หรืออย่างน้อยก็เป็นความรู้ C ++ ที่ฉันชอบซึ่งคนส่วนใหญ่ไม่รู้

const MyClass& x = MyClass(); // temporary exists as long as x is in scope

3
คุณสามารถอธิบายได้หรือไม่? ตามที่คุณกำลังล้อเล่น;)
โจเซฟการ์วิน

8
ScopeGuard ( ddj.com/cpp/184403758 ) เป็นตัวอย่างที่ดีที่ใช้ประโยชน์จากคุณลักษณะนี้
MSN

2
ฉันอยู่กับโจเซฟการ์วิน โปรดให้ความรู้แก่เรา
Peter Mortensen

ฉันเพิ่งทำในความคิดเห็น นอกจากนี้ยังเป็นผลตามธรรมชาติของการใช้พารามิเตอร์อ้างอิง const
MSN


52

คุณลักษณะที่ดีที่ไม่ได้ใช้บ่อยคือบล็อกลองจับที่ใช้งานได้ทั่วทั้งฟังก์ชัน:

int Function()
try
{
   // do something here
   return 42;
}
catch(...)
{
   return -1;
}

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


ฉันไม่คิดว่าคุณจะสามารถreturnจับจาก Function Try ได้ แต่เพียงแค่สร้างใหม่
Constantin

ฉันเพิ่งลองรวบรวมข้อมูลข้างต้นและไม่มีคำเตือนใด ๆ ฉันคิดว่าตัวอย่างข้างต้นได้ผล
vividos

7
ผลตอบแทนถูกห้ามสำหรับผู้สร้างเท่านั้น ฟังก์ชัน try block ของตัวสร้างจะตรวจจับข้อผิดพลาดในการเริ่มต้นฐานและสมาชิก (กรณีเดียวที่บล็อกลองฟังก์ชันทำสิ่งที่แตกต่างจากการลองภายในฟังก์ชัน) การไม่ขว้างซ้ำจะส่งผลให้วัตถุไม่สมบูรณ์
puetzk

ใช่. สิ่งนี้มีประโยชน์มาก ฉันเขียนมาโคร BEGIN_COM_METHOD และ END_COM_METHOD เพื่อตรวจจับข้อยกเว้นและส่งคืน HRESULTS เพื่อไม่ให้ข้อยกเว้นรั่วไหลออกจากคลาสที่ใช้อินเตอร์เฟส COM มันทำงานได้ดี
Scott Langham

3
ตามที่ @puetzk ชี้ไว้นี่เป็นวิธีเดียวที่จะจัดการข้อยกเว้นที่เกิดจากสิ่งใด ๆในรายการเริ่มต้นของตัวสร้างเช่นตัวสร้างคลาสพื้นฐานหรือของสมาชิกข้อมูล
anton.burger

44

หลายคนรู้จักidentity/ idmetafunction แต่มี usecase ที่ดีสำหรับกรณีที่ไม่ใช่ template: Ease writing declarations:

// void (*f)(); // same
id<void()>::type *f;

// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);

// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];

// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;

ช่วยถอดรหัสการประกาศ C ++ ได้อย่างมาก!

// boost::identity is pretty much the same
template<typename T> 
struct id { typedef T type; };

ที่น่าสนใจ แต่แรกที่จริงผมมีมากขึ้นปัญหาในการอ่านบางส่วนของคำนิยามเหล่านั้น วิธีการแก้ไขปัญหาที่เกิดขึ้นภายในออกด้วย C ++ ประกาศก็คือการเขียนบางนามแฝงประเภทแม่แบบ: template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;-> pointer<function<void,int>> f(pointer<function<void,void>>);หรือpointer<void(int)> f(pointer<void()>);หรือfunction<pointer<function<void,int>>,pointer<function<void,void>>> f;
bames53

42

คุณลักษณะที่ซ่อนอยู่คือคุณสามารถกำหนดตัวแปรภายในเงื่อนไข if และขอบเขตของมันจะครอบคลุมเฉพาะ if และอื่น ๆ ที่บล็อก:

if(int * p = getPointer()) {
    // do something
}

มาโครบางตัวใช้สิ่งนั้นเพื่อให้ขอบเขต "ถูกล็อก" เช่นนี้:

struct MutexLocker { 
    MutexLocker(Mutex&);
    ~MutexLocker(); 
    operator bool() const { return false; } 
private:
    Mutex &m;
};

#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else 

void someCriticalPath() {
    locked(myLocker) { /* ... */ }
}

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

switch(int value = getIt()) {
    // ...
}

และในขณะที่วนซ้ำ:

while(SomeThing t = getSomeThing()) {
    // ...
}

(และยังอยู่ในเงื่อนไข) แต่ฉันไม่แน่ใจเหมือนกันว่าสิ่งเหล่านี้มีประโยชน์หรือไม่ :)


เรียบร้อย! ฉันไม่เคยรู้เลยว่าคุณทำได้ ... มันจะมี (และจะ) ประหยัดความยุ่งยากเมื่อเขียนโค้ดด้วยค่าส่งคืนข้อผิดพลาด มีวิธีใดบ้างที่จะยังคงมีเงื่อนไขแทนที่จะเป็นเพียง! = 0 ในรูปแบบนี้? ถ้า ((int r = func ()) <0) ดูเหมือนจะไม่ทำงาน ...
puetzk

puetzk ไม่มีไม่มี แต่ดีใจที่คุณชอบ :)
Johannes Schaub - litb

4
@Frerich สิ่งนี้ไม่สามารถทำได้ในรหัส C เลย ฉันคิดว่าคุณกำลังคิดif((a = f()) == b) ...แต่คำตอบนี้ประกาศตัวแปรในเงื่อนไข
Johannes Schaub - litb

1
@ โกรธมันแตกต่างกันมากเพราะการประกาศตัวแปรจะถูกทดสอบสำหรับค่าบูลีนทันที มีการแมปไปยัง for-loops ด้วยซึ่งดูเหมือนว่าfor(...; int i = foo(); ) ...;สิ่งนี้จะผ่านร่างกายตราบเท่าที่iเป็นจริงเริ่มต้นใหม่ทุกครั้งอีกครั้ง ลูปที่คุณแสดงเป็นเพียงการแสดงให้เห็นถึงการประกาศตัวแปร แต่ไม่ใช่การประกาศตัวแปรที่ทำหน้าที่เป็นเงื่อนไขพร้อมกัน :)
Johannes Schaub - litb

5
ดีมากยกเว้นว่าคุณไม่ได้กล่าวถึงการใช้งานที่ตั้งใจไว้สำหรับคุณลักษณะนี้สำหรับตัวชี้แบบไดนามิกฉันเชื่อ
mmocny

29

การป้องกันไม่ให้ตัวดำเนินการลูกน้ำเรียกตัวดำเนินการเกินพิกัด

บางครั้งคุณใช้ตัวดำเนินการลูกน้ำอย่างถูกต้อง แต่คุณต้องการให้แน่ใจว่าไม่มีตัวดำเนินการลูกน้ำที่ผู้ใช้กำหนดไว้เข้ามาขวางทางเพราะตัวอย่างเช่นคุณต้องอาศัยจุดลำดับระหว่างด้านซ้ายและด้านขวาหรือต้องการให้แน่ใจว่าไม่มีสิ่งใดขัดขวางสิ่งที่ต้องการ หนังบู๊. นี่คือที่void()มาของเกม:

for(T i, j; can_continue(i, j); ++i, void(), ++j)
  do_code(i, j);

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


ฉันเพิ่งใช้สิ่งนี้เพื่อจบการเพิกเฉยต่อการแสดงออกเกินจริง :)
GManNickG

28

การเริ่มต้นอาร์เรย์ในตัวสร้าง ตัวอย่างเช่นในคลาสถ้าเรามีอาร์เรย์intเป็น:

class clName
{
  clName();
  int a[10];
};

เราสามารถเริ่มต้นองค์ประกอบทั้งหมดในอาร์เรย์เป็นค่าเริ่มต้น (ที่นี่องค์ประกอบทั้งหมดของอาร์เรย์เป็นศูนย์) ในตัวสร้างเป็น:

clName::clName() : a()
{
}

6
คุณสามารถทำได้กับอาร์เรย์ใดก็ได้
Potatoswatter

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

ถ้าประเภทของอาร์เรย์เป็นประเภทคลาสก็ไม่จำเป็นใช่ไหม
Thomas Eding

27

โอฉันสามารถหารายชื่อสัตว์เลี้ยงที่เกลียดชังแทนได้:

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

ในด้านบวก

  • คุณลักษณะที่ซ่อนอยู่: ฟังก์ชั่นลองบล็อก น่าเสียดายที่ฉันไม่พบการใช้งาน ใช่ฉันรู้ว่าทำไมพวกเขาเพิ่มมัน แต่คุณต้องสร้างใหม่ในตัวสร้างซึ่งทำให้มันไม่มีจุดหมาย
  • ควรดูอย่างรอบคอบที่ STL รับประกันเกี่ยวกับความถูกต้องของตัววนซ้ำหลังจากการปรับเปลี่ยนคอนเทนเนอร์ซึ่งจะช่วยให้คุณสร้างลูปที่ดีขึ้นเล็กน้อย
  • Boost - แทบจะไม่เป็นความลับ แต่ก็คุ้มค่าที่จะใช้
  • การเพิ่มประสิทธิภาพการคืนค่า (ไม่ชัดเจน แต่ได้รับอนุญาตโดยเฉพาะตามมาตรฐาน)
  • Functors aka function objects aka operator () สิ่งนี้ถูกใช้อย่างกว้างขวางโดย STL ไม่ใช่ความลับ แต่เป็นผลข้างเคียงที่ดีของการใช้งานตัวดำเนินการมากเกินไปและเทมเพลต

16
ความเกลียดชังสัตว์เลี้ยง: ไม่มี ABI ที่กำหนดไว้สำหรับแอป C ++ ซึ่งแตกต่างจากภาษา C ที่ทุกคนใช้เพราะทุกภาษาสามารถรับประกันได้ว่าจะเรียกใช้ฟังก์ชัน C ไม่มีใครสามารถทำเช่นเดียวกันกับ C ++ ได้
gbjbaanb

8
ผู้ทำลายจะต้องเป็นเสมือนก็ต่อเมื่อคุณตั้งใจที่จะทำลายโพลีมอร์ฟิเชียลซึ่งแตกต่างจากจุดแรกเล็กน้อย
David Rodríguez - น้ำลายไหล

2
ด้วยชนิดโลคัล C ++ 0x สามารถใช้เป็นพารามิเตอร์เทมเพลตได้
tstenner

1
ด้วย C ++ 0x ผู้ทำลายจะเป็นเสมือนถ้าวัตถุมีฟังก์ชันเสมือนใด ๆ (เช่น vtable)
Macke

อย่าลืม NRVO และแน่นอนว่าการเพิ่มประสิทธิภาพใด ๆ จะได้รับอนุญาตตราบเท่าที่ไม่เปลี่ยนแปลงผลลัพธ์ของโปรแกรม
jk

26

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

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

struct A {
protected:
    int a;
};

struct B : A {
    // error: can't access protected member
    static int get(A &x) { return x.a; }
};

struct C : A { };

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

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            // error: stack<int>::c is protected
            return s.c;
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

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

struct A {
protected:
    int a;
};

struct B : A {
    // valid: *can* access protected member
    static int get(A &x) { return x.*(&B::a); }
};

struct C : A { };

และแน่นอนมันยังใช้ได้กับstd::stackตัวอย่าง

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            return s.*(pillager::c);
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

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

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        using std::stack<int>::c;
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = s.*(&pillager::c);
}


26

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

template<typename Func1, typename Func2>
class callable {
  Func1 *m_f1;
  Func2 *m_f2;

public:
  callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
  operator Func1*() { return m_f1; }
  operator Func2*() { return m_f2; }
};

void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }

int main() {
  callable<void(int), void(long)> c(foo, bar);
  c(42); // calls foo
  c(42L); // calls bar
}

สิ่งเหล่านี้เรียกว่า "ฟังก์ชันการโทรตัวแทน"


1
เมื่อคุณบอกว่าการแก้ปัญหาโอเวอร์โหลดเสร็จสิ้นจากผลลัพธ์ของพวกเขาคุณหมายความว่ามันแปลงเป็น Functors ทั้งสองจริงหรือไม่และจากนั้นความละเอียดโอเวอร์โหลดหรือไม่? ฉันลองพิมพ์บางอย่างในตัวดำเนินการ Func1 * () และตัวดำเนินการ Func2 * () แต่ดูเหมือนว่าจะเลือกสิ่งที่ถูกต้องเมื่อพบว่าจะเรียกใช้ตัวดำเนินการแปลงใด
เครื่องนำทาง

3
@navigator ใช่มันแปลงแนวความคิดเป็นทั้งคู่แล้วเลือกสิ่งที่ดีที่สุด ไม่จำเป็นต้องเรียกพวกเขาจริง ๆ เพราะมันรู้จากชนิดของผลลัพธ์ว่าพวกเขาจะให้อะไรอยู่แล้ว การโทรจริงจะเกิดขึ้นเมื่อปรากฎว่ามีการเลือกอะไรในที่สุด
Johannes Schaub - litb

26

คุณสมบัติที่ซ่อนอยู่:

  1. ฟังก์ชันเสมือนจริงสามารถใช้งานได้ ตัวอย่างทั่วไปตัวทำลายเสมือนจริง
  2. หากฟังก์ชันแสดงข้อยกเว้นที่ไม่ได้ระบุไว้ในข้อกำหนดข้อยกเว้น แต่ฟังก์ชันนั้นมีstd::bad_exceptionอยู่ในข้อกำหนดข้อยกเว้นข้อยกเว้นจะถูกแปลงstd::bad_exceptionและโยนโดยอัตโนมัติ ด้วยวิธีนี้อย่างน้อยคุณก็จะรู้ว่าbad_exceptionถูกโยน อ่านเพิ่มเติมที่นี่

  3. ฟังก์ชั่นลองบล็อก

  4. คีย์เวิร์ดเทมเพลตในการลบความสับสนของ typedefs ในเทมเพลตคลาส หากชื่อของความเชี่ยวชาญเทมเพลตสมาชิกปรากฏหลัง a ., ->หรือ::ตัวดำเนินการและชื่อนั้นมีพารามิเตอร์เทมเพลตที่ตรงตามเกณฑ์อย่างชัดเจนให้นำหน้าชื่อเทมเพลตสมาชิกด้วยเทมเพลตคำหลัก อ่านเพิ่มเติมที่นี่

  5. ค่าเริ่มต้นของพารามิเตอร์ฟังก์ชันสามารถเปลี่ยนแปลงได้ที่รันไทม์ อ่านเพิ่มเติมที่นี่

  6. A[i] ทำงานได้ดีพอ ๆ i[A]

  7. สามารถแก้ไขอินสแตนซ์ชั่วคราวของคลาสได้! ฟังก์ชัน non-const member สามารถเรียกใช้บนอ็อบเจ็กต์ชั่วคราว ตัวอย่างเช่น:

    struct Bar {
      void modify() {}
    }
    int main (void) {
      Bar().modify();   /* non-const function invoked on a temporary. */
    }

    อ่านเพิ่มเติมที่นี่

  8. หากมีสองประเภทที่แตกต่างกันอยู่ก่อนและหลังนิพจน์ตัวดำเนินการ:ใน ternary ( ?:) ดังนั้นชนิดผลลัพธ์ของนิพจน์จะเป็นประเภทที่มีลักษณะทั่วไปมากที่สุดของทั้งสอง ตัวอย่างเช่น:

    void foo (int) {}
    void foo (double) {}
    struct X {
      X (double d = 0.0) {}
    };
    void foo (X) {} 
    
    int main(void) {
      int i = 1;
      foo(i ? 0 : 0.0); // calls foo(double)
      X x;
      foo(i ? 0.0 : x);  // calls foo(X)
    }

P Daddy: A [i] == * (A + i) == * (i + A) == i [A]
abelenky

ฉันได้รับการสับเปลี่ยนนั่นหมายความว่า [] ไม่มีค่าความหมายของตัวมันเองและเทียบเท่ากับการแทนที่สไตล์มาโครโดยที่ "x [y]" ถูกแทนที่ด้วย "(* ((x) + (y )))" ไม่ใช่สิ่งที่ฉันคาดหวัง ฉันสงสัยว่าทำไมมันถึงกำหนดแบบนี้
P Daddy

ความเข้ากันได้ย้อนหลังกับ C
jmucchiello

2
เกี่ยวกับจุดแรกของคุณ: มีกรณีพิเศษอย่างหนึ่งที่คุณต้องใช้ฟังก์ชันเสมือนจริงนั่นคือตัวทำลายเสมือนจริง
Frerich Raabe

24

map::operator[]สร้างรายการหากไม่มีคีย์และส่งกลับการอ้างอิงไปยังค่ารายการที่สร้างขึ้นเริ่มต้น ดังนั้นคุณสามารถเขียน:

map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
  s.assign(...);
}
cout << s;

ฉันประหลาดใจที่มีโปรแกรมเมอร์ C ++ ไม่รู้เรื่องนี้กี่คน


11
และในทางตรงกันข้ามคุณไม่สามารถใช้ตัวดำเนินการ [] บนแผนที่ const ได้
David Rodríguez - dribeas

2
+1 .find()สำหรับนิคคนสามารถไปบ้าถ้าพวกเขาไม่ทราบเกี่ยวกับ
LiraNuna

หรือ " const map::operator[]สร้างข้อความแสดงข้อผิดพลาด"
แค่ใครสักคน

2
ไม่ใช่คุณลักษณะของภาษา แต่เป็นคุณลักษณะของไลบรารีเทมเพลตมาตรฐาน นอกจากนี้ยังค่อนข้างชัดเจนเนื่องจากตัวดำเนินการ [] ส่งคืนข้อมูลอ้างอิงที่ถูกต้อง
Ramon Zarazua B.

2
ฉันต้องใช้แผนที่ใน C # สักพักหนึ่งซึ่งแผนที่ไม่ทำงานในลักษณะนั้นเพื่อให้รู้ว่านี่เป็นคุณสมบัติ ฉันคิดว่าฉันรำคาญมันมากกว่าที่ฉันใช้ แต่ดูเหมือนว่าฉันคิดผิด ฉันหายไปใน C #
sbi

20

การใส่ฟังก์ชันหรือตัวแปรในเนมสเปซแบบไม่ระบุชื่อจะไม่สนับสนุนการใช้staticเพื่อ จำกัด ขอบเขตของไฟล์


"deprecates" เป็นคำที่แข็งแกร่ง…
Potatoswatter

@ Potato: ความคิดเห็นเก่าฉันรู้ แต่มาตรฐานบอกว่าการใช้สแตติกในขอบเขตเนมสเปซนั้นเลิกใช้แล้วโดยมีค่ากำหนดสำหรับเนมสเปซที่ไม่มีชื่อ
GManNickG

@GMan: ไม่มีพร็อบฉันไม่คิดว่าหน้า SO จะ "ตาย" จริงๆ สำหรับทั้งสองด้านของเรื่องราวstaticในขอบเขตทั่วโลกจะไม่เลิกใช้งาน แต่อย่างใด (อ้างอิง: C ++ 03 §D.2)
Potatoswatter

อ่าเมื่ออ่านอย่างใกล้ชิด "ชื่อที่ประกาศในเนมสเปซส่วนกลางมีขอบเขตเนมสเปซสากล (หรือที่เรียกว่าขอบเขตทั่วโลก)" หมายความว่าอย่างนั้นจริงเหรอ?
Potatoswatter

@ Potato: ใช่. :) staticควรใช้ในประเภทคลาสหรือฟังก์ชันเท่านั้น
GManNickG

19

การกำหนดฟังก์ชันเพื่อนธรรมดาในเทมเพลตชั้นเรียนต้องให้ความสนใจเป็นพิเศษ:

template <typename T> 
class Creator { 
    friend void appear() {  // a new function ::appear(), but it doesn't 
                           // exist until Creator is instantiated 
    } 
};
Creator<void> miracle;  // ::appear() is created at this point 
Creator<double> oops;   // ERROR: ::appear() is created a second time! 

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

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

template <typename T> 
class Creator { 
    friend void feed(Creator<T>*){  // every T generates a different 
                                   // function ::feed() 
    } 
}; 

Creator<void> one;     // generates ::feed(Creator<void>*) 
Creator<double> two;   // generates ::feed(Creator<double>*) 

คำเตือน: ฉันได้วางส่วนนี้จากเทมเพลต C ++: The Complete Guide / Section 8.4


18

ฟังก์ชันโมฆะสามารถคืนค่าโมฆะ

ไม่ค่อยมีใครรู้จัก แต่รหัสต่อไปนี้ใช้ได้

void f() { }
void g() { return f(); }

เช่นเดียวกับสิ่งที่ดูแปลก ๆ ต่อไปนี้

void f() { return (void)"i'm discarded"; }

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

template<typename T>
struct sample {
  // assume f<T> may return void
  T dosomething() { return f<T>(); }

  // better than T t = f<T>(); /* ... */ return t; !
};

17

อ่านไฟล์เป็นเวกเตอร์ของสตริง:

 vector<string> V;
 copy(istream_iterator<string>(cin), istream_iterator<string>(),
     back_inserter(V));

istream_iterator


8
หรือ: vector <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
UncleBens

5
คุณหมายถึงvector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- ไม่มีวงเล็บหลังพารามิเตอร์ตัวที่สอง
knittl

1
นี่ไม่ใช่คุณลักษณะ C ++ ที่ซ่อนอยู่ คุณสมบัติเพิ่มเติมของ STL STL! = a language
Nick Bedford

14

คุณสามารถเทมเพลต bitfields

template <size_t X, size_t Y>
struct bitfield
{
    char left  : X;
    char right : Y;
};

ฉันยังไม่ได้มีจุดประสงค์ใด ๆ สำหรับสิ่งนี้ แต่มันทำให้ฉันประหลาดใจอย่างแน่นอน


1
ดูที่นี่ที่ฉันเพิ่งแนะนำสำหรับการคำนวณแบบ n-bit: stackoverflow.com/questions/8309538/…
sehe

14

หนึ่งในไวยากรณ์ที่น่าสนใจที่สุดของภาษาโปรแกรมใด ๆ

สามสิ่งนี้อยู่ด้วยกันและสองสิ่งที่แตกต่างกันโดยสิ้นเชิง ...

SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));

ทั้งหมดยกเว้นที่สามและห้ากำหนดSomeTypeอ็อบเจ็กต์บนสแต็กและเริ่มต้น ( uในสองกรณีแรกและคอนสตรัคเตอร์เริ่มต้นในกรณีที่สี่ส่วนที่สามคือการประกาศฟังก์ชันที่ไม่มีพารามิเตอร์และส่งกลับค่าSomeTypeที่ห้าคือการประกาศในทำนองเดียวกัน ฟังก์ชั่นที่ใช้พารามิเตอร์หนึ่งโดยมูลค่าของประเภทชื่อSomeTypeu


มีความแตกต่างระหว่างวันที่ 1 และ 2 หรือไม่? แม้ว่าฉันรู้ว่าทั้งสองเป็นการเริ่มต้น
Özgür

Comptrol: ฉันไม่คิดอย่างนั้น ทั้งสองจะเรียกตัวสร้างการคัดลอกแม้ว่าตัวแรกจะดูเหมือนตัวดำเนินการกำหนด แต่ก็เป็นตัวสร้างการคัดลอก
abelenky

1
หากคุณเป็นประเภทอื่นจาก SomeType ประเภทแรกจะเรียกตัวสร้างการแปลงก่อนจากนั้นตัวสร้างการคัดลอกในขณะที่ตัวสร้างที่สองจะเรียกตัวสร้างการแปลงเท่านั้น
Eclipse

3
1st คือการเรียกโดยนัยของตัวสร้าง, 2nd คือการเรียกที่ชัดเจน ดูโค้ดต่อไปนี้เพื่อดูความแตกต่าง #include <iostream> class sss {public: Explicit sss (int) {std :: cout << "int" << std :: endl; }; sss (สองเท่า) {std :: cout << "double" << std :: endl; }; }; int หลัก () {sss ddd (7); // เรียก int constructor sss xxx = 7; // เรียก double constructor return 0; }
Kirill V. Lyadvinsky

จริง - บรรทัดแรกจะไม่ทำงานหากมีการประกาศตัวสร้างอย่างชัดเจน
Eclipse

12

การกำจัดการประกาศไปข้างหน้า:

struct global
{
     void main()
     {
           a = 1;
           b();
     }
     int a;
     void b(){}
}
singleton;

การเขียนคำสั่งสลับด้วย?: ตัวดำเนินการ:

string result = 
    a==0 ? "zero" :
    a==1 ? "one" :
    a==2 ? "two" :
    0;

ทำทุกอย่างในบรรทัดเดียว:

void a();
int b();
float c = (a(),b(),1.0f);

Zeroing โครงสร้างโดยไม่มี memset:

FStruct s = {0};

Normalizing / ตัดมุมและเวลา - ค่า:

int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150

การกำหนดการอ้างอิง:

struct ref
{
   int& r;
   ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;

2
FStruct s = {};ยิ่งสั้นลง
Constantin

ในตัวอย่างสุดท้ายมันจะง่ายกว่าด้วย: a (); ข (); ลอย c = 1.0f;
Zifre

2
ไวยากรณ์นี้ "float c = (a (), b (), 1.0f);" มีประโยชน์สำหรับการเน้นการดำเนินการมอบหมาย (การกำหนด "c") การมอบหมายงานมีความสำคัญในการเขียนโปรแกรมเนื่องจากมีโอกาสน้อยที่จะกลายเป็น IMO ที่เลิกใช้แล้ว ไม่รู้ว่าทำไมอาจเป็นสิ่งที่เกี่ยวข้องกับการเขียนโปรแกรมเชิงฟังก์ชันซึ่งสถานะโปรแกรมจะถูกกำหนดใหม่ทุกเฟรม PS และไม่ "int d = (11,22,1.0f)" จะเท่ากับ "1" ทดสอบเมื่อนาทีที่แล้วกับ VS2008
AareP

2
+1 คุณไม่ควรโทร main ? ฉันขอแนะนำglobal().main();และลืมเกี่ยวกับซิงเกิลตันไป ( คุณสามารถทำงานร่วมกับชั่วคราวซึ่งจะขยายอายุการใช้งาน )
ดู

1
ฉันสงสัยว่าการกำหนดข้อมูลอ้างอิงเป็นแบบพกพา ฉันชอบโครงสร้างที่จะยกเว้นการประกาศไปข้างหน้า
Thomas Eding

12

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

กล่าวอีกนัยหนึ่งเราสามารถเขียนนิพจน์ C ++ ที่ถูกต้องต่อไปนี้โดยใช้ตัว?:ดำเนินการ

i = a > b ? a : throw something();

BTW ความจริงที่ว่า throw expression เป็นนิพจน์ (ประเภทvoid) และไม่ใช่คำสั่งเป็นอีกหนึ่งคุณลักษณะที่ไม่ค่อยมีใครรู้จักของภาษา C ++ ซึ่งหมายความว่าโค้ดต่อไปนี้ใช้ได้อย่างสมบูรณ์แบบ

void foo()
{
  return throw something();
}

แม้ว่าจะไม่มีประเด็นมากนักในการทำเช่นนี้ (บางทีในโค้ดเทมเพลตทั่วไปบางอย่างอาจมีประโยชน์)


สิ่งที่คุ้มค่านีลมีคำถามเกี่ยวกับสิ่งนี้: stackoverflow.com/questions/1212978/…สำหรับข้อมูลเพิ่มเติม
GManNickG

12

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

struct A { void f() { } };

struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };

// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };

ฉันเคยใช้สิ่งนี้เพื่อใช้การสนับสนุนการจัดตำแหน่งซึ่งจะระบุการจัดตำแหน่งที่เข้มงวดที่สุดโดยอัตโนมัติโดยใช้กฎการครอบงำ

สิ่งนี้ไม่เพียงใช้กับฟังก์ชันเสมือนเท่านั้น แต่ยังรวมถึงชื่อ typedef สมาชิกแบบคงที่ / ไม่ใช่เสมือนและสิ่งอื่น ๆ ฉันเคยเห็นมันใช้เพื่อใช้ลักษณะที่เขียนทับได้ในโปรแกรมเมตา


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