ไม่ชอบ C ++ เมื่อพูดถึงบรรทัดคำถาม "คุณลักษณะที่ซ่อนอยู่" หรือไม่? คิดว่าฉันจะโยนมันออกไปที่นั่น อะไรคือคุณสมบัติที่ซ่อนอยู่ของ C ++?
ไม่ชอบ C ++ เมื่อพูดถึงบรรทัดคำถาม "คุณลักษณะที่ซ่อนอยู่" หรือไม่? คิดว่าฉันจะโยนมันออกไปที่นั่น อะไรคือคุณสมบัติที่ซ่อนอยู่ของ C ++?
คำตอบ:
โปรแกรมเมอร์ C ++ ส่วนใหญ่คุ้นเคยกับตัวดำเนินการ ternary:
x = (y < 0) ? 10 : 20;
อย่างไรก็ตามพวกเขาไม่ทราบว่าสามารถใช้เป็น lvalue ได้:
(a == 0 ? a : b) = 1;
ซึ่งเป็นชวเลขสำหรับ
if (a == 0)
a = 1;
else
b = 1;
ใช้ด้วยความระมัดระวัง :-)
(value ? function1 : function2)()
แต่ฉันรู้สึกประหลาดใจที่จะพบนี้ยังทำงาน:
function1
และfunction2
ถูกแปลงเป็นตัวชี้ฟังก์ชันโดยนัยและผลลัพธ์จะถูกแปลงกลับโดยปริยาย
คุณสามารถใส่ URI ลงในซอร์ส C ++ ได้โดยไม่มีข้อผิดพลาด ตัวอย่างเช่น:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
goto
C ++ ซึ่งมี) สิ่งที่ตามหลังเครื่องหมายทับสองตัวคือความคิดเห็น จึงมีhttp://stackoverflow.com
, http
เป็นฉลาก (คุณในทางทฤษฎีสามารถเขียนgoto http;
) และ//stackoverflow.com
เป็นเพียงความคิดเห็นที่สิ้นสุดของเส้น ทั้งสองอย่างนี้เป็น C ++ ที่ถูกกฎหมายดังนั้นโครงสร้างจึงคอมไพล์ แน่นอนว่ามันไม่ได้ทำประโยชน์อะไรอย่างคลุมเครือ
goto http;
ไม่ได้ติดตาม URL จริงๆ :(
ตัวชี้เลขคณิต
โปรแกรมเมอร์ C ++ ชอบที่จะหลีกเลี่ยงพอยน์เตอร์เนื่องจากมีบั๊กที่สามารถแนะนำได้
C ++ ที่เจ๋งที่สุดที่ฉันเคยเห็นมา? ตัวอักษรอนาล็อก
ฉันเห็นด้วยกับโพสต์ส่วนใหญ่ที่นั่น: 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 )
คุณลักษณะภาษาหนึ่งที่ฉันคิดว่าค่อนข้างซ่อนเร้นเพราะฉันไม่เคยได้ยินเกี่ยวกับเรื่องนี้มาตลอดช่วงเวลาที่เรียนในโรงเรียนคือนามแฝงเนมสเปซ ฉันไม่ได้รับความสนใจจนกว่าฉันจะพบตัวอย่างของมันในเอกสารการเพิ่มประสิทธิภาพ แน่นอนตอนนี้ฉันรู้แล้วคุณสามารถค้นหาได้ในการอ้างอิง C ++ มาตรฐานใด ๆ
namespace fs = boost::filesystem;
fs::path myPath( strPath, fs::native );
using
ผมคิดว่านี้จะเป็นประโยชน์ถ้าคุณไม่ต้องการที่จะใช้
ไม่เพียง แต่สามารถประกาศตัวแปรในส่วนเริ่มต้นของfor
ลูปเท่านั้น แต่ยังรวมถึงคลาสและฟังก์ชันด้วย
for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
...
}
ที่อนุญาตให้มีตัวแปรหลายประเภทที่แตกต่างกัน
ตัวดำเนินการอาร์เรย์เชื่อมโยงกัน
A [8] เป็นคำพ้องความหมายของ * (A + 8) เนื่องจากการบวกเป็นความเชื่อมโยงจึงสามารถเขียนใหม่เป็น * (8 + A) ซึ่งเป็นคำพ้องความหมายของ ..... 8 [A]
คุณไม่ได้บอกว่ามีประโยชน์ ... :-)
A
ไม่สำคัญเลย ตัวอย่างเช่นหากA
เป็น a char*
รหัสจะยังคงใช้ได้
สิ่งหนึ่งที่ไม่ค่อยมีใครรู้ก็คือสหภาพแรงงานสามารถเป็นแม่แบบได้เช่นกัน:
template<typename From, typename To>
union union_cast {
From from;
To to;
union_cast(From from)
:from(from) { }
To getTo() const { return to; }
};
และสามารถมีตัวสร้างและฟังก์ชันสมาชิกได้ด้วย ไม่มีอะไรเกี่ยวข้องกับการสืบทอด (รวมถึงฟังก์ชันเสมือน)
From
และTo
มีการตั้งค่าและใช้งานตามความเหมาะสม การรวมกันดังกล่าวสามารถใช้กับพฤติกรรมที่กำหนดได้แม้ว่า (โดยTo
เป็นอาร์เรย์ของถ่านที่ไม่ได้ลงนามหรือโครงสร้างที่ใช้ลำดับเริ่มต้นร่วมFrom
กัน แม้ว่าคุณจะใช้ในลักษณะที่ไม่ได้กำหนด แต่ก็ยังอาจมีประโยชน์สำหรับงานระดับต่ำ อย่างไรก็ตามนี่เป็นเพียงตัวอย่างหนึ่งของเทมเพลตยูเนี่ยน - อาจมีการใช้งานอื่น ๆ สำหรับสหภาพเทมเพลต
C ++ เป็นมาตรฐานไม่ควรมีคุณสมบัติแอบแฝง ...
C ++ เป็นภาษาที่มีหลายกระบวนทัศน์คุณสามารถเดิมพันด้วยเงินก้อนสุดท้ายของคุณว่ามีคุณสมบัติที่ซ่อนอยู่ ตัวอย่างหนึ่งจากหลายแม่แบบ metaprogramming ไม่มีใครในคณะกรรมการมาตรฐานที่ตั้งใจให้มีภาษาย่อยที่สมบูรณ์แบบทัวริงซึ่งได้รับการดำเนินการในเวลาคอมไพล์
คุณสมบัติที่ซ่อนอยู่อีกอย่างที่ใช้ไม่ได้ใน 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
}
อายุการใช้งานของ temporaries ที่เชื่อมโยงกับการอ้างอิง const เป็นสิ่งที่คนไม่กี่คนรู้ หรืออย่างน้อยก็เป็นความรู้ C ++ ที่ฉันชอบซึ่งคนส่วนใหญ่ไม่รู้
const MyClass& x = MyClass(); // temporary exists as long as x is in scope
คุณลักษณะที่ดีที่ไม่ได้ใช้บ่อยคือบล็อกลองจับที่ใช้งานได้ทั่วทั้งฟังก์ชัน:
int Function()
try
{
// do something here
return 42;
}
catch(...)
{
return -1;
}
การใช้งานหลักคือการแปลข้อยกเว้นไปยังคลาสข้อยกเว้นอื่น ๆ และการสร้างใหม่หรือแปลระหว่างข้อยกเว้นและการจัดการรหัสข้อผิดพลาดตามผลตอบแทน
return
จับจาก Function Try ได้ แต่เพียงแค่สร้างใหม่
หลายคนรู้จักidentity
/ id
metafunction แต่มี 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; };
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;
คุณลักษณะที่ซ่อนอยู่คือคุณสามารถกำหนดตัวแปรภายในเงื่อนไข 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()) {
// ...
}
(และยังอยู่ในเงื่อนไข) แต่ฉันไม่แน่ใจเหมือนกันว่าสิ่งเหล่านี้มีประโยชน์หรือไม่ :)
if((a = f()) == b) ...
แต่คำตอบนี้ประกาศตัวแปรในเงื่อนไข
for(...; int i = foo(); ) ...;
สิ่งนี้จะผ่านร่างกายตราบเท่าที่i
เป็นจริงเริ่มต้นใหม่ทุกครั้งอีกครั้ง ลูปที่คุณแสดงเป็นเพียงการแสดงให้เห็นถึงการประกาศตัวแปร แต่ไม่ใช่การประกาศตัวแปรที่ทำหน้าที่เป็นเงื่อนไขพร้อมกัน :)
บางครั้งคุณใช้ตัวดำเนินการลูกน้ำอย่างถูกต้อง แต่คุณต้องการให้แน่ใจว่าไม่มีตัวดำเนินการลูกน้ำที่ผู้ใช้กำหนดไว้เข้ามาขวางทางเพราะตัวอย่างเช่นคุณต้องอาศัยจุดลำดับระหว่างด้านซ้ายและด้านขวาหรือต้องการให้แน่ใจว่าไม่มีสิ่งใดขัดขวางสิ่งที่ต้องการ หนังบู๊. นี่คือที่void()
มาของเกม:
for(T i, j; can_continue(i, j); ++i, void(), ++j)
do_code(i, j);
ไม่สนใจตัวยึดตำแหน่งที่ฉันใส่เงื่อนไขและรหัส สิ่งที่สำคัญคือสิ่งvoid()
ที่ทำให้คอมไพเลอร์บังคับให้ใช้ตัวดำเนินการลูกน้ำในตัว สิ่งนี้จะมีประโยชน์เมื่อนำคลาสลักษณะไปใช้ในบางครั้งเช่นกัน
การเริ่มต้นอาร์เรย์ในตัวสร้าง ตัวอย่างเช่นในคลาสถ้าเรามีอาร์เรย์int
เป็น:
class clName
{
clName();
int a[10];
};
เราสามารถเริ่มต้นองค์ประกอบทั้งหมดในอาร์เรย์เป็นค่าเริ่มต้น (ที่นี่องค์ประกอบทั้งหมดของอาร์เรย์เป็นศูนย์) ในตัวสร้างเป็น:
clName::clName() : a()
{
}
โอฉันสามารถหารายชื่อสัตว์เลี้ยงที่เกลียดชังแทนได้:
ในด้านบวก
คุณสามารถเข้าถึงข้อมูลที่มีการป้องกันและสมาชิกฟังก์ชันของคลาสใด ๆ โดยไม่ต้องมีพฤติกรรมที่ไม่ได้กำหนดและด้วยความหมายที่คาดหวัง อ่านต่อเพื่อดูวิธีการ อ่านรายงานข้อบกพร่องด้วยเกี่ยวกับเรื่องนี้ด้วย
โดยปกติ 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);
}
คุณสมบัติที่ซ่อนอยู่อีกประการหนึ่งคือคุณสามารถเรียกคลาสอ็อบเจ็กต์ที่สามารถแปลงเป็นฟังก์ชันพอยน์เตอร์หรือการอ้างอิงได้ ความละเอียดเกินจะกระทำกับผลลัพธ์ของพวกเขาและอาร์กิวเมนต์จะถูกส่งต่ออย่างสมบูรณ์แบบ
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
}
สิ่งเหล่านี้เรียกว่า "ฟังก์ชันการโทรตัวแทน"
คุณสมบัติที่ซ่อนอยู่:
หากฟังก์ชันแสดงข้อยกเว้นที่ไม่ได้ระบุไว้ในข้อกำหนดข้อยกเว้น แต่ฟังก์ชันนั้นมีstd::bad_exception
อยู่ในข้อกำหนดข้อยกเว้นข้อยกเว้นจะถูกแปลงstd::bad_exception
และโยนโดยอัตโนมัติ ด้วยวิธีนี้อย่างน้อยคุณก็จะรู้ว่าbad_exception
ถูกโยน อ่านเพิ่มเติมที่นี่
ฟังก์ชั่นลองบล็อก
คีย์เวิร์ดเทมเพลตในการลบความสับสนของ typedefs ในเทมเพลตคลาส หากชื่อของความเชี่ยวชาญเทมเพลตสมาชิกปรากฏหลัง a .
, ->
หรือ::
ตัวดำเนินการและชื่อนั้นมีพารามิเตอร์เทมเพลตที่ตรงตามเกณฑ์อย่างชัดเจนให้นำหน้าชื่อเทมเพลตสมาชิกด้วยเทมเพลตคำหลัก อ่านเพิ่มเติมที่นี่
ค่าเริ่มต้นของพารามิเตอร์ฟังก์ชันสามารถเปลี่ยนแปลงได้ที่รันไทม์ อ่านเพิ่มเติมที่นี่
A[i]
ทำงานได้ดีพอ ๆ i[A]
สามารถแก้ไขอินสแตนซ์ชั่วคราวของคลาสได้! ฟังก์ชัน non-const member สามารถเรียกใช้บนอ็อบเจ็กต์ชั่วคราว ตัวอย่างเช่น:
struct Bar {
void modify() {}
}
int main (void) {
Bar().modify(); /* non-const function invoked on a temporary. */
}
หากมีสองประเภทที่แตกต่างกันอยู่ก่อนและหลังนิพจน์ตัวดำเนินการ:
ใน 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)
}
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 ++ ไม่รู้เรื่องนี้กี่คน
.find()
สำหรับนิคคนสามารถไปบ้าถ้าพวกเขาไม่ทราบเกี่ยวกับ
const map::operator[]
สร้างข้อความแสดงข้อผิดพลาด"
การใส่ฟังก์ชันหรือตัวแปรในเนมสเปซแบบไม่ระบุชื่อจะไม่สนับสนุนการใช้static
เพื่อ จำกัด ขอบเขตของไฟล์
static
ในขอบเขตทั่วโลกจะไม่เลิกใช้งาน แต่อย่างใด (อ้างอิง: C ++ 03 §D.2)
static
ควรใช้ในประเภทคลาสหรือฟังก์ชันเท่านั้น
การกำหนดฟังก์ชันเพื่อนธรรมดาในเทมเพลตชั้นเรียนต้องให้ความสนใจเป็นพิเศษ:
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
ไม่ค่อยมีใครรู้จัก แต่รหัสต่อไปนี้ใช้ได้
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; !
};
อ่านไฟล์เป็นเวกเตอร์ของสตริง:
vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));
vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());
- ไม่มีวงเล็บหลังพารามิเตอร์ตัวที่สอง
คุณสามารถเทมเพลต bitfields
template <size_t X, size_t Y>
struct bitfield
{
char left : X;
char right : Y;
};
ฉันยังไม่ได้มีจุดประสงค์ใด ๆ สำหรับสิ่งนี้ แต่มันทำให้ฉันประหลาดใจอย่างแน่นอน
หนึ่งในไวยากรณ์ที่น่าสนใจที่สุดของภาษาโปรแกรมใด ๆ
สามสิ่งนี้อยู่ด้วยกันและสองสิ่งที่แตกต่างกันโดยสิ้นเชิง ...
SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));
ทั้งหมดยกเว้นที่สามและห้ากำหนดSomeType
อ็อบเจ็กต์บนสแต็กและเริ่มต้น ( u
ในสองกรณีแรกและคอนสตรัคเตอร์เริ่มต้นในกรณีที่สี่ส่วนที่สามคือการประกาศฟังก์ชันที่ไม่มีพารามิเตอร์และส่งกลับค่าSomeType
ที่ห้าคือการประกาศในทำนองเดียวกัน ฟังก์ชั่นที่ใช้พารามิเตอร์หนึ่งโดยมูลค่าของประเภทชื่อSomeType
u
การกำจัดการประกาศไปข้างหน้า:
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;
FStruct s = {};
ยิ่งสั้นลง
main
? ฉันขอแนะนำglobal().main();
และลืมเกี่ยวกับซิงเกิลตันไป ( คุณสามารถทำงานร่วมกับชั่วคราวซึ่งจะขยายอายุการใช้งาน )
ตัวดำเนินการที่มีเงื่อนไขตามเงื่อนไข?:
กำหนดให้ตัวถูกดำเนินการที่สองและสามต้องมีประเภท "ยอมรับได้" (พูดอย่างไม่เป็นทางการ) แต่ข้อกำหนดนี้มีข้อยกเว้นอย่างหนึ่ง (ตั้งใจให้เล่นสำนวน): ตัวถูกดำเนินการที่สองหรือสามอาจเป็นนิพจน์โยน (ซึ่งมีประเภทvoid
) โดยไม่คำนึงถึงประเภทของตัวถูกดำเนินการอื่น ๆ
กล่าวอีกนัยหนึ่งเราสามารถเขียนนิพจน์ C ++ ที่ถูกต้องต่อไปนี้โดยใช้ตัว?:
ดำเนินการ
i = a > b ? a : throw something();
BTW ความจริงที่ว่า throw expression เป็นนิพจน์ (ประเภทvoid
) และไม่ใช่คำสั่งเป็นอีกหนึ่งคุณลักษณะที่ไม่ค่อยมีใครรู้จักของภาษา C ++ ซึ่งหมายความว่าโค้ดต่อไปนี้ใช้ได้อย่างสมบูรณ์แบบ
void foo()
{
return throw something();
}
แม้ว่าจะไม่มีประเด็นมากนักในการทำเช่นนี้ (บางทีในโค้ดเทมเพลตทั่วไปบางอย่างอาจมีประโยชน์)
กฎการปกครองมีประโยชน์ แต่ไม่ค่อยมีใครรู้ มันบอกว่าแม้ว่าจะอยู่ในพา ธ ที่ไม่ซ้ำกันผ่านโครงตาข่ายคลาสพื้นฐานการค้นหาชื่อสำหรับสมาชิกที่ซ่อนบางส่วนจะไม่ซ้ำกันหากสมาชิกอยู่ในคลาสฐานเสมือน:
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 สมาชิกแบบคงที่ / ไม่ใช่เสมือนและสิ่งอื่น ๆ ฉันเคยเห็นมันใช้เพื่อใช้ลักษณะที่เขียนทับได้ในโปรแกรมเมตา
struct C
ในตัวอย่างของคุณ ... ? ไชโย