อ้างอิงจากไลบรารีมาตรฐาน C ++: แบบฝึกหัดและคู่มือ :
วิธีพกพาวิธีเดียวในการใช้เทมเพลตในขณะนี้คือการนำไปใช้ในไฟล์ส่วนหัวโดยใช้ฟังก์ชั่นแบบอินไลน์
ทำไมนี้
(การชี้แจง: ไฟล์ส่วนหัวไม่ได้เป็นโซลูชั่นแบบพกพาเท่านั้นแต่เป็นวิธีแบบพกพาที่สะดวกที่สุด)
อ้างอิงจากไลบรารีมาตรฐาน C ++: แบบฝึกหัดและคู่มือ :
วิธีพกพาวิธีเดียวในการใช้เทมเพลตในขณะนี้คือการนำไปใช้ในไฟล์ส่วนหัวโดยใช้ฟังก์ชั่นแบบอินไลน์
ทำไมนี้
(การชี้แจง: ไฟล์ส่วนหัวไม่ได้เป็นโซลูชั่นแบบพกพาเท่านั้นแต่เป็นวิธีแบบพกพาที่สะดวกที่สุด)
คำตอบ:
Caveat: มันไม่จำเป็นที่จะทำให้การใช้งานในไฟล์ส่วนหัวดูวิธีการแก้ปัญหาทางเลือกในตอนท้ายของคำตอบนี้
อย่างไรก็ตามเหตุผลที่รหัสของคุณล้มเหลวก็คือเมื่อสร้างแม่แบบอินสแตนซ์คอมไพเลอร์จะสร้างคลาสใหม่ด้วยอาร์กิวเมนต์แม่แบบที่กำหนด ตัวอย่างเช่น:
template<typename T>
struct Foo
{
T bar;
void doSomething(T param) {/* do stuff using T */}
};
// somewhere in a .cpp
Foo<int> f;
เมื่ออ่านบรรทัดนี้คอมไพเลอร์จะสร้างคลาสใหม่ (เรียกว่าFooInt
) ซึ่งเทียบเท่ากับสิ่งต่อไปนี้:
struct FooInt
{
int bar;
void doSomething(int param) {/* do stuff using int */}
}
ดังนั้นคอมไพเลอร์จำเป็นต้องมีการเข้าถึงการใช้งานของวิธีการเพื่อยกตัวอย่างพวกเขาด้วยอาร์กิวเมนต์แม่แบบ (ในกรณีนี้int
) หากการใช้งานเหล่านี้ไม่ได้อยู่ในส่วนหัวพวกเขาจะไม่สามารถเข้าถึงได้และคอมไพเลอร์จะไม่สามารถยกตัวอย่างเทมเพลต
วิธีแก้ไขปัญหาทั่วไปนี้คือการเขียนการประกาศแม่แบบในไฟล์ส่วนหัวจากนั้นนำคลาสมาใช้ในไฟล์การนำไปใช้ (เช่น. tpp) และรวมไฟล์การนำไปใช้นี้ที่ส่วนท้ายของส่วนหัว
foo.h
template <typename T>
struct Foo
{
void doSomething(T param);
};
#include "Foo.tpp"
Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}
วิธีนี้การใช้งานจะยังคงแยกออกจากการประกาศ แต่สามารถเข้าถึงคอมไพเลอร์
อีกวิธีหนึ่งคือแยกการติดตั้งใช้งานและอินสแตนซ์เทมเพลตอินสแตนซ์ทั้งหมดที่คุณต้องการ:
foo.h
// no implementation
template <typename T> struct Foo { ... };
Foo.cpp
// implementation of Foo's methods
// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float
หากคำอธิบายของฉันยังไม่ชัดเจนพอคุณสามารถดูคำถามที่พบบ่อย C ++ Super-FAQ ในหัวข้อนี้
คำตอบที่ถูกต้องมากมายที่นี่ แต่ฉันต้องการเพิ่ม (เพื่อความสมบูรณ์):
หากคุณอยู่ที่ด้านล่างของไฟล์ cpp สำหรับการใช้งานให้ทำอินสแตนซ์ของประเภททั้งหมดที่เทมเพลตจะใช้ด้วยโดยชัดแจ้งตัวเชื่อมโยงจะสามารถค้นหาได้ตามปกติ
แก้ไข: การเพิ่มตัวอย่างการสร้างอินสแตนซ์เทมเพลตอย่างชัดเจน ใช้หลังจากที่มีการกำหนดแม่แบบและฟังก์ชั่นสมาชิกทั้งหมดได้รับการกำหนด
template class vector<int>;
สิ่งนี้จะยกตัวอย่าง (และทำให้ผู้เชื่อมโยงใช้งาน) ชั้นเรียนและฟังก์ชั่นสมาชิกทั้งหมด (เท่านั้น) ไวยากรณ์ที่คล้ายกันใช้ได้กับฟังก์ชั่นเทมเพลตดังนั้นหากคุณมีโอเปอเรเตอร์ที่ไม่ใช่สมาชิกมากเกินไปคุณอาจต้องทำสิ่งเดียวกันสำหรับสิ่งเหล่านั้น
ตัวอย่างข้างต้นค่อนข้างไร้ประโยชน์เนื่องจากเวกเตอร์ถูกกำหนดไว้ในส่วนหัวยกเว้นเมื่อไฟล์รวมทั่วไป (ส่วนหัวที่คอมไพล์แล้ว?) ใช้extern template class vector<int>
เพื่อป้องกันไม่ให้อินสแตนซ์นั้นในไฟล์อื่น ๆ (1000?) ที่ใช้เวกเตอร์
type
โดยไม่ต้องแสดงรายการด้วยตนเอง
vector
ไม่ใช่ตัวอย่างที่ดีเนื่องจากคอนเทนเนอร์มีการกำหนดเป้าหมายตามประเภท "ทั้งหมด" โดยเนื้อแท้ แต่เกิดขึ้นบ่อยครั้งมากที่คุณสร้างเทมเพลตที่มีความหมายเฉพาะสำหรับชุดประเภทที่เฉพาะเจาะจงเช่นประเภทตัวเลข: int8_t, int16_t, int32_t, uint8_t, uint16_t เป็นต้นในกรณีนี้คุณควรใช้เทมเพลต แต่การสร้างอินสแตนซ์ให้กับประเภททั้งหมดอย่างชัดเจนนั้นเป็นไปได้และในความคิดของฉันก็แนะนำ
.cpp
ไฟล์ของชั้นเรียนและอินสแตนซ์ที่สองถูกอ้างถึงจาก.cpp
ไฟล์อื่น ๆและฉันยังคงได้รับข้อผิดพลาดในการเชื่อมโยงที่ไม่พบสมาชิก
เป็นเพราะความต้องการในการรวบรวมแยกต่างหากและเนื่องจากแม่แบบนั้นมีความหลากหลายในรูปแบบการสร้างอินสแตนซ์
ให้คำอธิบายใกล้เคียงกับคอนกรีตเล็กน้อย ว่าฉันมีไฟล์ต่อไปนี้:
class MyClass<T>
class MyClass<T>
MyClass<int>
เฉพาะกิจการรวบรวมวิธีการที่ฉันควรจะสามารถรวบรวมfoo.cppเป็นอิสระจากbar.cpp คอมไพเลอร์ทำงานอย่างหนักในการวิเคราะห์การปรับให้เหมาะสมและการสร้างโค้ดในแต่ละหน่วยการรวบรวมโดยอิสระอย่างสมบูรณ์ เราไม่จำเป็นต้องทำการวิเคราะห์ทั้งโปรแกรม มันเป็นเพียงลิงเกอร์ที่ต้องจัดการกับโปรแกรมทั้งหมดในครั้งเดียวและงานของลิงเกอร์นั้นง่ายกว่ามาก
bar.cppไม่จำเป็นต้องมีอยู่เมื่อฉันรวบรวมfoo.cppแต่ฉันก็ควรจะสามารถเชื่อมโยงfoo.oฉันได้ร่วมกับbar.oฉันเพิ่งผลิตโดยไม่ต้อง recompile foo .cpp foo.cppได้แม้จะเรียบเรียงห้องสมุดแบบไดนามิกกระจายที่อื่นโดยไม่ต้องfoo.cppและเชื่อมโยงกับรหัสที่พวกเขาเขียนปีหลังจากที่ผมเขียนfoo.cpp
"Instantiation สไตล์แตกต่าง" หมายความว่าแม่แบบไม่ได้จริงๆระดับทั่วไปที่สามารถรวบรวมรหัสที่สามารถทำงานสำหรับค่าใดMyClass<T>
ๆ T
ที่จะเพิ่มค่าใช้จ่ายเช่นมวยจำเป็นต้องส่งผ่านคำแนะนำการทำงานในการจัดสรรและการก่อสร้าง ฯลฯ ความตั้งใจของ C ++ แม่คือการหลีกเลี่ยงที่จะเขียนเหมือนกันเกือบclass MyClass_int
, class MyClass_float
ฯลฯ แต่ยังคงสามารถที่จะจบลงด้วยรหัสเรียบเรียงที่เป็น ส่วนใหญ่ราวกับว่าเราได้เขียนแต่ละฉบับแยกกัน ดังนั้นแม่แบบตัวอักษรแม่แบบ; เทมเพลตของชั้นเรียนไม่ใช่คลาส แต่เป็นสูตรสำหรับสร้างคลาสใหม่สำหรับแต่ละครั้งที่T
เราพบ เทมเพลตไม่สามารถคอมไพล์ลงในโค้ดได้เฉพาะผลลัพธ์ของอินสแตนซ์ของเทมเพลตที่สามารถรวบรวมได้
ดังนั้นเมื่อคอมไพล์foo.cppคอมไพเลอร์จะไม่เห็นbar.cpp ที่จะรู้ว่าMyClass<int>
จำเป็น มันสามารถเห็นเทมเพลตMyClass<T>
แต่ไม่สามารถปล่อยรหัสสำหรับมัน (เป็นเทมเพลตไม่ใช่คลาส) และเมื่อคอมไพล์bar.cppคอมไพเลอร์จะเห็นว่ามันจำเป็นต้องสร้างMyClass<int>
แต่มันไม่สามารถเห็นเทมเพลตMyClass<T>
(เฉพาะส่วนต่อประสานในfoo.h ) ดังนั้นจึงไม่สามารถสร้างได้
หากfoo.cppใช้ตัวเองMyClass<int>
รหัสนั้นจะถูกสร้างขึ้นในขณะที่รวบรวมfoo.cppดังนั้นเมื่อbar.oเชื่อมโยงกับfoo.oพวกเขาสามารถถูกเชื่อมโยงและจะทำงานได้ เราสามารถใช้ข้อเท็จจริงนี้เพื่ออนุญาตให้มีการใช้ชุดอินสแตนซ์ของแม่แบบที่ จำกัด ในไฟล์. cpp โดยการเขียนเทมเพลตเดียว แต่ไม่มีวิธีใดที่bar.cppจะใช้เทมเพลตเป็นเทมเพลตและยกตัวอย่างให้กับสิ่งที่มันชอบ มันสามารถใช้ได้เฉพาะรุ่นที่มีอยู่แล้วของคลาส templated ที่ผู้เขียนfoo.cppคิดที่จะให้
คุณอาจคิดว่าเมื่อรวบรวมเทมเพลตคอมไพเลอร์ควร "สร้างทุกรุ่น" โดยที่ไม่เคยใช้ถูกกรองออกระหว่างการเชื่อมโยง นอกเหนือจากค่าโสหุ้ยขนาดใหญ่และความยากลำบากมากเช่นวิธีการที่จะเผชิญเพราะ "ตัวแก้ไขประเภท" คุณสมบัติเช่นตัวชี้และอาร์เรย์ให้แม้เพียงชนิดในตัวที่ก่อให้เกิดประเภทจำนวนอนันต์เกิดอะไรขึ้นเมื่อฉันขยายโปรแกรมของฉัน โดยการเพิ่ม:
class BazPrivate
และใช้งานMyClass<BazPrivate>
ไม่มีทางที่เป็นไปได้ที่สิ่งนี้จะสามารถทำงานได้ถ้าเราไม่
MyClass<T>
MyClass<T>
เพื่อให้คอมไพเลอร์สามารถสร้างMyClass<BazPrivate>
ในระหว่างการรวบรวมของbaz.cppไม่มีใครชอบ (1) เพราะระบบการรวบรวมทั้งการวิเคราะห์โปรแกรมใช้เวลาในการรวบรวมตลอดไปและเพราะมันเป็นไปไม่ได้ที่จะแจกจ่ายไลบรารีที่คอมไพล์โดยไม่มีซอร์สโค้ด ดังนั้นเราจึงมี (2) แทน
เทมเพลตจำเป็นต้องมีอินสแตนซ์ของคอมไพเลอร์ก่อนรวบรวมเป็นโค้ด การสร้างอินสแตนซ์นี้สามารถทำได้ก็ต่อเมื่อรู้ข้อโต้แย้งของเทมเพลต ตอนนี้คิดว่าสถานการณ์ที่ฟังก์ชั่นแม่แบบจะมีการประกาศในa.h
ที่กำหนดไว้ในและนำมาใช้ในa.cpp
b.cpp
เมื่อa.cpp
รวบรวมแล้วไม่จำเป็นว่าการรวบรวมที่จะเกิดขึ้นb.cpp
จะต้องมีอินสแตนซ์ของเทมเพลต สำหรับไฟล์ส่วนหัวและแหล่งที่มาเพิ่มเติมสถานการณ์อาจซับซ้อนขึ้นอย่างรวดเร็ว
หนึ่งสามารถยืนยันว่าคอมไพเลอร์สามารถทำให้ฉลาดขึ้นเพื่อ "มองไปข้างหน้า" สำหรับการใช้งานของแม่แบบทั้งหมด แต่ฉันแน่ใจว่ามันจะไม่ยากที่จะสร้างสถานการณ์แบบเรียกซ้ำหรือซับซ้อน AFAIK คอมไพเลอร์ไม่ทำหน้าตาแบบนั้น ดังที่ Anton ชี้ให้เห็นคอมไพเลอร์บางตัวสนับสนุนการประกาศเอ็กซ์พอร์ตอย่างชัดเจนของอินสแตนซ์ของเท็มเพลต แต่ก็ยังมีคอมไพเลอร์ทั้งหมดที่สนับสนุน
ที่จริงก่อน C ++ 11 มาตรฐานกำหนดexport
คำสำคัญที่จะทำให้สามารถประกาศแม่แบบในไฟล์ส่วนหัวและนำไปใช้ที่อื่น
ไม่มีคอมไพเลอร์ยอดนิยมที่ใช้คำค้นหานี้ สิ่งเดียวที่ฉันรู้คือส่วนหน้าเขียนโดย Edison Design Group ซึ่งใช้โดยคอมไพเลอร์ Comeau C ++ ผู้อื่นทั้งหมดต้องการให้คุณเขียนเทมเพลตในไฟล์ส่วนหัวเนื่องจากคอมไพเลอร์ต้องการคำนิยามเทมเพลตสำหรับการสร้างอินสแตนซ์ที่เหมาะสม
ด้วยเหตุนี้คณะกรรมการมาตรฐาน ISO C ++ จึงตัดสินใจที่จะลบexport
คุณลักษณะของเทมเพลตด้วย C ++ 11
export
จะให้เราจริง ๆและสิ่งที่ไม่ ... และตอนนี้ฉันเห็นด้วยอย่างสุดซึ้งกับคน EDG: มันจะไม่ทำให้สิ่งที่คนส่วนใหญ่ (ตัวเองใน '11 รวมแล้ว) คิดว่าจะทำได้และมาตรฐาน C ++ จะดีกว่าหากไม่มี
แม้ว่า C ++ มาตรฐานจะไม่มีข้อกำหนดดังกล่าวคอมไพเลอร์บางตัวจำเป็นต้องใช้ฟังก์ชั่นและแม่แบบคลาสทั้งหมดเพื่อให้พร้อมใช้งานในทุกหน่วยการแปลที่ใช้ ผลสำหรับคอมไพเลอร์เหล่านั้นเนื้อความของฟังก์ชันเท็มเพลตต้องทำให้พร้อมใช้งานในไฟล์ส่วนหัว ในการทำซ้ำ: นั่นหมายถึงคอมไพเลอร์เหล่านั้นจะไม่อนุญาตให้กำหนดไว้ในไฟล์ที่ไม่ใช่ส่วนหัวเช่นไฟล์. cpp
มีคำสำคัญการส่งออกซึ่งควรจะบรรเทาปัญหานี้ แต่ก็ไม่มีที่ไหนใกล้เคียงกับการพกพา
เทมเพลตต้องใช้ในส่วนหัวเนื่องจากคอมไพเลอร์จำเป็นต้องสร้างอินสแตนซ์ของรหัสต่าง ๆ ขึ้นอยู่กับพารามิเตอร์ที่กำหนด / อนุมานสำหรับพารามิเตอร์เทมเพลต โปรดจำไว้ว่าแม่แบบไม่ได้แสดงรหัสโดยตรง แต่เป็นแม่แบบสำหรับรหัสหลายรุ่น เมื่อคุณคอมไพล์ฟังก์ชันที่ไม่ใช่เท็มเพลตใน.cpp
ไฟล์คุณกำลังรวบรวมฟังก์ชัน / คลาสที่เป็นรูปธรรม นี่ไม่ใช่กรณีของแม่แบบซึ่งสามารถสร้างอินสแตนซ์ที่มีประเภทต่าง ๆ ได้นั่นคือต้องมีการปล่อยโค้ดคอนกรีตเมื่อเปลี่ยนพารามิเตอร์เทมเพลตด้วยประเภทคอนกรีต
มีคุณสมบัติที่มีexport
คำหลักที่ใช้สำหรับการรวบรวมแยกต่างหาก export
คุณลักษณะจะเลิกในC++11
และ AFAIK เพียงหนึ่งคอมไพเลอร์นำมาใช้มัน export
คุณไม่ควรจะใช้ การรวบรวมแบบแยกส่วนนั้นเป็นไปไม่ได้ในC++
หรือC++11
อาจจะเป็นในC++17
กรณีที่แนวคิดเกิดขึ้นเราอาจมีวิธีการรวบรวมแบบแยก
เพื่อให้การรวบรวมแยกกันสำเร็จจะต้องมีการตรวจสอบเนื้อหาเทมเพลตแยกต่างหาก ดูเหมือนว่าวิธีการแก้ปัญหาเป็นไปได้กับแนวคิด ลองดูที่เอกสารนี้นำเสนอเมื่อเร็ว ๆ นี้ในการประชุมผู้รับมอบมาตรฐาน ฉันคิดว่านี่ไม่ใช่ข้อกำหนดเพียงอย่างเดียวเนื่องจากคุณยังต้องสร้างโค้ดสำหรับเทมเพลตในรหัสผู้ใช้
ปัญหาการรวบรวมแยกสำหรับเทมเพลตฉันคิดว่ามันเป็นปัญหาที่เกิดขึ้นกับการย้ายข้อมูลไปยังโมดูลซึ่งกำลังทำงานอยู่
หมายความว่าวิธีแบบพกพาที่สุดในการกำหนดการใช้งานเมธอดของคลาสเทมเพลตคือการกำหนดไว้ในนิยามคลาสเทมเพลต
template < typename ... >
class MyClass
{
int myMethod()
{
// Not just declaration. Add method implementation here
}
};
แม้ว่าจะมีคำอธิบายที่ดีมากมายอยู่ด้านบน แต่ฉันก็ขาดวิธีที่ใช้งานได้จริงในการแยกแม่แบบออกเป็นส่วนหัวและส่วนร่าง
ความกังวลหลักของฉันคือการหลีกเลี่ยงการคอมไพล์ใหม่ของผู้ใช้เทมเพลตทั้งหมดเมื่อฉันเปลี่ยนคำจำกัดความ
การมีอินสแตนซ์เทมเพลตทั้งหมดในเทมเพลตเทมเพลตไม่ใช่วิธีแก้ปัญหาสำหรับฉันเนื่องจากผู้เขียนเทมเพลตอาจไม่ทราบทั้งหมดหากการใช้งานและผู้ใช้เทมเพลตอาจไม่มีสิทธิ์ในการแก้ไข
ฉันใช้วิธีการต่อไปนี้ซึ่งใช้ได้กับคอมไพเลอร์รุ่นเก่า (gcc 4.3.4, aCC A.03.13)
สำหรับการใช้เทมเพลตแต่ละครั้งจะมี typedef ในไฟล์ส่วนหัวของตัวเอง (สร้างจากโมเดล UML) เนื้อหาของมันประกอบด้วยอินสแตนซ์ (ซึ่งสิ้นสุดในไลบรารีที่เชื่อมโยงในตอนท้าย)
ผู้ใช้เทมเพลตแต่ละคนมีไฟล์ส่วนหัวนั้นและใช้ typedef
ตัวอย่างแผนผัง:
MyTemplate.h:
#ifndef MyTemplate_h
#define MyTemplate_h 1
template <class T>
class MyTemplate
{
public:
MyTemplate(const T& rt);
void dump();
T t;
};
#endif
MyTemplate.cpp:
#include "MyTemplate.h"
#include <iostream>
template <class T>
MyTemplate<T>::MyTemplate(const T& rt)
: t(rt)
{
}
template <class T>
void MyTemplate<T>::dump()
{
cerr << t << endl;
}
MyInstantiatedTemplate.h:
#ifndef MyInstantiatedTemplate_h
#define MyInstantiatedTemplate_h 1
#include "MyTemplate.h"
typedef MyTemplate< int > MyInstantiatedTemplate;
#endif
MyInstantiatedTemplate.cpp:
#include "MyTemplate.cpp"
template class MyTemplate< int >;
main.cpp:
#include "MyInstantiatedTemplate.h"
int main()
{
MyInstantiatedTemplate m(100);
m.dump();
return 0;
}
วิธีนี้เฉพาะการสร้างอินสแตนซ์ของเทมเพลตเท่านั้นที่จะต้องทำการคอมไพล์ใหม่ไม่ใช่ผู้ใช้เทมเพลตทั้งหมด (และการขึ้นต่อกัน)
MyInstantiatedTemplate.h
ไฟล์และเพิ่มMyInstantiatedTemplate
ประเภท มันค่อนข้างสะอาดกว่าถ้าคุณไม่ใช้มัน ชำระเงินคำตอบของฉันสำหรับคำถามอื่นที่แสดงสิ่งนี้: stackoverflow.com/a/41292751/4612476
เพียงเพิ่มบางสิ่งที่สำคัญที่นี่ หนึ่งสามารถกำหนดวิธีการของคลาส templated ได้ดีในไฟล์การใช้งานเมื่อพวกเขาไม่ใช่แม่แบบฟังก์ชั่น
myQueue.hpp:
template <class T>
class QueueA {
int size;
...
public:
template <class T> T dequeue() {
// implementation here
}
bool isEmpty();
...
}
myQueue.cpp:
// implementation of regular methods goes like this:
template <class T> bool QueueA<T>::isEmpty() {
return this->size == 0;
}
main()
{
QueueA<char> Q;
...
}
isEmpty
จากหน่วยการแปลอื่นนอกเหนือจากmyQueue.cpp
...
หากข้อกังวลคือเวลาการรวบรวมพิเศษและการขยายขนาดฐานสองที่ผลิตโดยการคอมไพล์. h ซึ่งเป็นส่วนหนึ่งของโมดูล. cpp ทั้งหมดที่ใช้ในหลาย ๆ กรณีสิ่งที่คุณสามารถทำได้คือทำให้เทมเพลตคลาสลงมาจากคลาสพื้นฐานที่ไม่ทำให้เทมเพลท ส่วนที่ไม่ขึ้นอยู่กับประเภทของอินเตอร์เฟสและคลาสฐานนั้นสามารถมีการนำไปใช้ในไฟล์. cpp
class XBase
ทุกที่ที่ฉันต้องนำไปใช้template class X
วางชิ้นส่วนที่ขึ้นกับชนิดลงในX
และส่วนที่เหลือXBase
ทั้งหมด
นั่นถูกต้องเพราะคอมไพเลอร์ต้องทราบว่าเป็นประเภทใดสำหรับการจัดสรร ดังนั้นคลาสเทมเพลต, ฟังก์ชั่น, enums ฯลฯ จะต้องดำเนินการเช่นกันในไฟล์ส่วนหัวถ้ามันจะทำให้เป็นสาธารณะหรือเป็นส่วนหนึ่งของห้องสมุด (คงที่หรือแบบไดนามิก) เพราะไฟล์ส่วนหัวจะไม่รวบรวมเหมือนไฟล์ c / cpp เป็น หากคอมไพเลอร์ไม่ทราบว่าประเภทนั้นไม่สามารถรวบรวมได้ ใน. Net มันสามารถเพราะวัตถุทั้งหมดได้มาจากชั้นวัตถุ นี่ไม่ใช่. Net
คอมไพเลอร์จะสร้างรหัสสำหรับแต่ละอินสแตนซ์ของอินสแตนซ์เมื่อคุณใช้เทมเพลตในระหว่างขั้นตอนการรวบรวม ในการรวบรวมและเชื่อมโยงไฟล์. cpp กระบวนการจะถูกแปลงเป็นวัตถุบริสุทธิ์หรือรหัสเครื่องซึ่งในนั้นมีการอ้างอิงหรือสัญลักษณ์ที่ไม่ได้กำหนดเพราะไฟล์. h ที่รวมอยู่ใน main.cpp ของคุณไม่มีการใช้งาน YET สิ่งเหล่านี้พร้อมที่จะเชื่อมโยงกับไฟล์วัตถุอื่นที่กำหนดการใช้งานสำหรับแม่แบบของคุณและทำให้คุณมี a.out แบบเต็ม
อย่างไรก็ตามเนื่องจากแม่แบบจะต้องดำเนินการในขั้นตอนการรวบรวมเพื่อสร้างรหัสสำหรับแต่ละอินสแตนซ์ของการเริ่มต้นแม่แบบที่คุณกำหนดดังนั้นเพียงแค่รวบรวมแม่แบบที่แยกต่างหากจากไฟล์ส่วนหัวของมันจะไม่ทำงานเพราะพวกเขาไปด้วยกันเสมอ แต่ละอินสแตนซ์ instantiation เป็นคลาสใหม่ทั้งหมด ในชั้นเรียนปกติคุณสามารถแยก. h และ. cpp ได้เนื่องจาก. h เป็นพิมพ์เขียวของคลาสนั้นและ. cpp เป็นการนำไปใช้จริงดังนั้นไฟล์การใช้งานใด ๆ ที่สามารถรวบรวมและเชื่อมโยงเป็นประจำได้อย่างไรก็ตามการใช้เทมเพลต. h เป็นพิมพ์เขียว คลาสไม่ควรมองว่าวัตถุควรมีความหมายอย่างไรกับไฟล์เทมเพลต. cpp ไม่ใช่การนำคลาสไปใช้เป็นประจำ แต่เป็นเพียงพิมพ์เขียวสำหรับคลาสดังนั้นการใช้เทมเพลตไฟล์. h สามารถทำได้ '
ดังนั้นเท็มเพลตจะไม่ถูกรวบรวมแยกต่างหากและจะรวบรวมเฉพาะในทุกที่ที่คุณมีอินสแตนซ์ที่เป็นรูปธรรมในไฟล์ต้นฉบับบางไฟล์ อย่างไรก็ตามการสร้างอินสแตนซ์ที่เป็นรูปธรรมจำเป็นต้องรู้ถึงการใช้งานไฟล์เทมเพลตเพราะเพียงแค่ปรับเปลี่ยนtypename T
การใช้รูปแบบที่เป็นรูปธรรมในไฟล์. h จะไม่ทำงานเนื่องจากมีไฟล์. cpp เชื่อมโยงอยู่ฉันไม่สามารถหาได้ในภายหลังเพราะจำแม่แบบเป็นนามธรรมและไม่สามารถรวบรวมได้ดังนั้นฉันจึงถูกบังคับ เพื่อให้การนำไปใช้งานในตอนนี้ดังนั้นฉันจึงรู้ว่าจะรวบรวมและเชื่อมโยงกันอย่างไรและตอนนี้ฉันมีการนำไปปฏิบัติแล้วมันจะเชื่อมโยงไปยังไฟล์ต้นฉบับที่ล้อมรอบ โดยทั่วไปช่วงเวลาที่ฉันสร้างอินสแตนซ์เทมเพลตที่ฉันต้องการสร้างคลาสใหม่ทั้งหมดและฉันไม่สามารถทำเช่นนั้นได้หากฉันไม่รู้ว่าคลาสนั้นควรมีลักษณะอย่างไรเมื่อใช้ประเภทที่ฉันให้ไว้เว้นแต่ฉันจะแจ้งให้ผู้รวบรวมทราบ การใช้เทมเพลตดังนั้นตอนนี้คอมไพเลอร์สามารถแทนที่T
ด้วยประเภทของฉันและสร้างคลาสคอนกรีตที่พร้อมที่จะรวบรวมและเชื่อมโยง
เพื่อสรุปผลเทมเพลตคือพิมพ์เขียวสำหรับลักษณะของคลาสที่ควรใช้คลาสเป็นพิมพ์เขียวสำหรับลักษณะของวัตถุ ฉันไม่สามารถคอมไพล์เท็มเพลตที่แยกต่างหากจากการสร้างอินสแตนซ์ที่เป็นรูปธรรมของพวกเขาเนื่องจากคอมไพเลอร์รวบรวมเฉพาะรูปแบบคอนกรีตกล่าวอีกอย่างคือเทมเพลตอย่างน้อยใน C ++ นั้นเป็นสิ่งที่เป็นนามธรรมอย่างแท้จริง เราต้องเทมเพลตที่ไม่เป็นนามธรรมเพื่อที่จะพูดและเราทำเช่นนั้นโดยให้พวกเขามีรูปแบบที่เป็นรูปธรรมในการจัดการเพื่อให้สิ่งที่เป็นนามธรรมของเทมเพลตของเราสามารถเปลี่ยนเป็นไฟล์คลาสปกติและในที่สุดก็สามารถรวบรวมได้ตามปกติ การแยกไฟล์เทมเพลต. h และไฟล์เทมเพลต. cpp นั้นไม่มีความหมาย มันเป็นเรื่องไร้สาระเพราะการแยก. cpp และ. h เท่านั้นเป็นเพียงที่. cpp สามารถรวบรวมเป็นรายบุคคลและเชื่อมโยงเป็นรายบุคคลด้วยแม่แบบเนื่องจากเราไม่สามารถรวบรวมแยกต่างหากเพราะแม่แบบเป็นสิ่งที่เป็นนามธรรม
ความหมายtypename T
ของ get ถูกแทนที่ระหว่างขั้นตอนการคอมไพล์ไม่ใช่ขั้นตอนการเชื่อมโยงดังนั้นถ้าฉันพยายามรวบรวมเท็มเพลตโดยไม่T
ถูกแทนที่เป็นประเภทค่าที่เป็นรูปธรรมซึ่งไม่มีความหมายอย่างสมบูรณ์ต่อคอมไพเลอร์และเนื่องจากไม่สามารถสร้างรหัสวัตถุได้ รู้ว่าT
คืออะไร
ในทางเทคนิคแล้วมันเป็นไปได้ที่จะสร้างฟังก์ชั่นบางอย่างที่จะบันทึกไฟล์ template.cpp และเปลี่ยนประเภทเมื่อพบมันในแหล่งอื่น ๆ ฉันคิดว่ามาตรฐานนั้นมีคำสำคัญexport
ที่จะช่วยให้คุณใส่แม่แบบแยกกัน ไฟล์ cpp แต่ไม่ใช่ว่าคอมไพเลอร์จำนวนมากจะใช้สิ่งนี้
เพียงบันทึกด้านข้างเมื่อสร้างความเชี่ยวชาญสำหรับคลาสเทมเพลตคุณสามารถแยกส่วนหัวออกจากการใช้งานเพราะความเชี่ยวชาญตามคำจำกัดความหมายความว่าฉันมีความเชี่ยวชาญสำหรับประเภทที่เป็นรูปธรรมที่สามารถรวบรวมและเชื่อมโยงเป็นรายบุคคล
วิธีที่จะมีการใช้งานแยกเป็นดังนี้
//inner_foo.h
template <typename T>
struct Foo
{
void doSomething(T param);
};
//foo.tpp
#include "inner_foo.h"
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}
//foo.h
#include <foo.tpp>
//main.cpp
#include <foo.h>
inner_foo มีการประกาศไปข้างหน้า foo.tpp มีการนำไปใช้และรวมถึง inner_foo.h; และ foo.h จะมีเพียงหนึ่งบรรทัดเพื่อรวม foo.tpp
ในเวลารวบรวมเนื้อหาของ foo.h จะถูกคัดลอกไปยัง foo.tpp จากนั้นไฟล์ทั้งหมดจะถูกคัดลอกไปยัง foo.h หลังจากนั้นจะรวบรวม ด้วยวิธีนี้ไม่มีข้อ จำกัด และการตั้งชื่อนั้นสอดคล้องกันเพื่อแลกเปลี่ยนไฟล์เสริมหนึ่งไฟล์
ฉันทำเช่นนี้เพราะการวิเคราะห์แบบคงที่สำหรับการแบ่งรหัสเมื่อไม่เห็นการประกาศไปข้างหน้าของชั้นเรียนใน * .tpp สิ่งนี้น่ารำคาญเมื่อเขียนโค้ดใน IDE ใด ๆ หรือใช้ YouCompleteMe หรืออื่น ๆ
ฉันขอแนะนำให้ดูหน้า gcc นี้ซึ่งกล่าวถึงการแลกเปลี่ยนระหว่างรูปแบบ "cfront" และ "borland" เพื่อสร้างอินสแตนซ์ของเทมเพลต
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html
รูปแบบ "บอร์แลนด์" สอดคล้องกับสิ่งที่ผู้เขียนแนะนำให้ใช้นิยามเทมเพลตเต็มรูปแบบและรวบรวมสิ่งต่าง ๆ หลายครั้ง
มันมีคำแนะนำที่ชัดเจนเกี่ยวกับการใช้การเริ่มต้นด้วยตนเองและแม่แบบอัตโนมัติ ตัวอย่างเช่นตัวเลือก "-repo" สามารถใช้เพื่อรวบรวมเทมเพลตซึ่งจำเป็นต้องมีอินสแตนซ์ หรืออีกตัวเลือกหนึ่งคือปิดใช้งานการสร้างแม่แบบอัตโนมัติโดยใช้ "-fno-implicit-templates" เพื่อบังคับใช้การสร้างแม่แบบด้วยตนเอง
จากประสบการณ์ของฉันฉันใช้ไลบรารีมาตรฐาน C ++ และเทมเพลต Boost ที่อินสแตนซ์สำหรับแต่ละหน่วยการคอมไพล์ (โดยใช้เทมเพลตไลบรารี) สำหรับคลาสเทมเพลตขนาดใหญ่ของฉันฉันจะสร้างอินสแตนซ์เทมเพลตด้วยตนเองทันทีสำหรับประเภทที่ฉันต้องการ
นี่เป็นวิธีการของฉันเพราะฉันกำลังจัดหาโปรแกรมที่ใช้งานได้ไม่ใช่ไลบรารีเทมเพลตสำหรับใช้ในโปรแกรมอื่น ผู้แต่งหนังสือเล่มนี้ Josuttis ทำงานในไลบรารีแม่แบบมากมาย
หากฉันกังวลเกี่ยวกับความเร็วฉันคิดว่าฉันจะสำรวจโดยใช้ส่วนหัวแบบคอมไพล์ล่วงหน้า https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
ซึ่งกำลังได้รับการสนับสนุนในคอมไพเลอร์จำนวนมาก อย่างไรก็ตามฉันคิดว่าส่วนหัวที่คอมไพล์แล้วจะยากสำหรับไฟล์ส่วนหัวเทมเพลต
อีกเหตุผลหนึ่งที่ควรเขียนทั้งการประกาศและคำจำกัดความในไฟล์ส่วนหัวนั้นเพื่อความสะดวกในการอ่าน สมมติว่ามีฟังก์ชันเทมเพลตอยู่ใน Utility.h:
template <class T>
T min(T const& one, T const& theOther);
และใน Utility.cpp:
#include "Utility.h"
template <class T>
T min(T const& one, T const& other)
{
return one < other ? one : other;
}
นี่ต้องใช้คลาส T ทุกตัวที่นี่เพื่อใช้โอเปอเรเตอร์น้อยกว่า (<) มันจะโยนข้อผิดพลาดคอมไพเลอร์เมื่อคุณเปรียบเทียบสองคลาสอินสแตนซ์ที่ไม่ได้ใช้ "<"
ดังนั้นหากคุณแยกการประกาศเทมเพลตและคำจำกัดความคุณจะไม่สามารถอ่านเฉพาะไฟล์ส่วนหัวเพื่อดูรายละเอียดของเทมเพลตนี้เพื่อใช้ API นี้ในคลาสของคุณเองแม้ว่าคอมไพเลอร์จะบอกคุณในสิ่งนี้ กรณีที่ผู้ประกอบการที่จะต้องถูกแทนที่
คุณสามารถกำหนดคลาสเทมเพลตของคุณภายในไฟล์. temp แทนไฟล์. cpp ใครก็ตามที่บอกว่าคุณสามารถกำหนดได้เฉพาะในไฟล์ส่วนหัวเท่านั้น นี่คือสิ่งที่ใช้งานได้จนถึง c ++ 98
อย่าลืมให้คอมไพเลอร์ปฏิบัติต่อไฟล์. temem ของคุณเป็นไฟล์ c ++
นี่คือตัวอย่างของสิ่งนี้สำหรับคลาสอาเรย์แบบไดนามิก
#ifndef dynarray_h
#define dynarray_h
#include <iostream>
template <class T>
class DynArray{
int capacity_;
int size_;
T* data;
public:
explicit DynArray(int size = 0, int capacity=2);
DynArray(const DynArray& d1);
~DynArray();
T& operator[]( const int index);
void operator=(const DynArray<T>& d1);
int size();
int capacity();
void clear();
void push_back(int n);
void pop_back();
T& at(const int n);
T& back();
T& front();
};
#include "dynarray.template" // this is how you get the header file
#endif
ในตอนนี้ภายในไฟล์. template ที่คุณกำหนดฟังก์ชั่นตามปกติ
template <class T>
DynArray<T>::DynArray(int size, int capacity){
if (capacity >= size){
this->size_ = size;
this->capacity_ = capacity;
data = new T[capacity];
}
// for (int i = 0; i < size; ++i) {
// data[i] = 0;
// }
}
template <class T>
DynArray<T>::DynArray(const DynArray& d1){
//clear();
//delete [] data;
std::cout << "copy" << std::endl;
this->size_ = d1.size_;
this->capacity_ = d1.capacity_;
data = new T[capacity()];
for(int i = 0; i < size(); ++i){
data[i] = d1.data[i];
}
}
template <class T>
DynArray<T>::~DynArray(){
delete [] data;
}
template <class T>
T& DynArray<T>::operator[]( const int index){
return at(index);
}
template <class T>
void DynArray<T>::operator=(const DynArray<T>& d1){
if (this->size() > 0) {
clear();
}
std::cout << "assign" << std::endl;
this->size_ = d1.size_;
this->capacity_ = d1.capacity_;
data = new T[capacity()];
for(int i = 0; i < size(); ++i){
data[i] = d1.data[i];
}
//delete [] d1.data;
}
template <class T>
int DynArray<T>::size(){
return size_;
}
template <class T>
int DynArray<T>::capacity(){
return capacity_;
}
template <class T>
void DynArray<T>::clear(){
for( int i = 0; i < size(); ++i){
data[i] = 0;
}
size_ = 0;
capacity_ = 2;
}
template <class T>
void DynArray<T>::push_back(int n){
if (size() >= capacity()) {
std::cout << "grow" << std::endl;
//redo the array
T* copy = new T[capacity_ + 40];
for (int i = 0; i < size(); ++i) {
copy[i] = data[i];
}
delete [] data;
data = new T[ capacity_ * 2];
for (int i = 0; i < capacity() * 2; ++i) {
data[i] = copy[i];
}
delete [] copy;
capacity_ *= 2;
}
data[size()] = n;
++size_;
}
template <class T>
void DynArray<T>::pop_back(){
data[size()-1] = 0;
--size_;
}
template <class T>
T& DynArray<T>::at(const int n){
if (n >= size()) {
throw std::runtime_error("invalid index");
}
return data[n];
}
template <class T>
T& DynArray<T>::back(){
if (size() == 0) {
throw std::runtime_error("vector is empty");
}
return data[size()-1];
}
template <class T>
T& DynArray<T>::front(){
if (size() == 0) {
throw std::runtime_error("vector is empty");
}
return data[0];
}