เหตุใดเทมเพลตจึงสามารถใช้ได้ในไฟล์ส่วนหัวเท่านั้น


1776

อ้างอิงจากไลบรารีมาตรฐาน C ++: แบบฝึกหัดและคู่มือ :

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

ทำไมนี้

(การชี้แจง: ไฟล์ส่วนหัวไม่ได้เป็นโซลูชั่นแบบพกพาเท่านั้นแต่เป็นวิธีแบบพกพาที่สะดวกที่สุด)


13
แม้ว่าการวางคำจำกัดความฟังก์ชันเทมเพลตทั้งหมดลงในไฟล์ส่วนหัวอาจเป็นวิธีที่สะดวกที่สุดในการใช้งาน แต่ก็ยังไม่ชัดเจนว่า "อินไลน์" กำลังทำอะไรอยู่ ไม่จำเป็นต้องใช้ฟังก์ชั่นอินไลน์สำหรับสิ่งนั้น "Inline" ไม่มีอะไรเกี่ยวข้องกับสิ่งนี้
AnT

7
หนังสือล้าสมัย
gerardw

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

5
ฉันเป็นคนเดียวที่รู้สึกว่าแนวคิดเทมเพลตพิการใน C ++ เนื่องจากสิ่งนี้หรือไม่ ...
DragonGamer

คำตอบ:


1557

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 ในหัวข้อนี้


96
ที่จริงแล้วการสร้างอินสแตนซ์ที่ชัดเจนนั้นต้องอยู่ในไฟล์. cpp ซึ่งสามารถเข้าถึงคำจำกัดความของฟังก์ชั่นสมาชิกของ Foo ได้ทั้งหมดแทนที่จะอยู่ในส่วนหัว
Mankarse

11
"คอมไพเลอร์จำเป็นต้องเข้าถึงการใช้งานเมธอดเพื่อยกตัวอย่างพวกเขาด้วยอาร์กิวเมนต์เท็มเพลต (ในกรณีนี้ int) หากการใช้งานเหล่านี้ไม่ได้อยู่ในส่วนหัวพวกเขาจะไม่สามารถเข้าถึงได้" แต่ทำไมการใช้งานใน ไฟล์. cpp ไม่สามารถเข้าถึงคอมไพเลอร์ได้หรือไม่ คอมไพเลอร์ยังสามารถเข้าถึงข้อมูล. cpp ได้จะเปลี่ยนเป็นไฟล์. obj ได้อย่างไร แก้ไข: คำตอบสำหรับคำถามนี้ในการเชื่อมโยงที่ระบุไว้ในคำตอบนี้ ...
xcrypt

31
ฉันไม่คิดว่านี่จะอธิบายคำถามที่ชัดเจนสิ่งสำคัญคือเห็นได้ชัดว่าเกี่ยวข้องกับการรวบรวม UNIT ซึ่งไม่ได้กล่าวถึงในบทความนี้
zinking

6
@Gabson: structs และคลาสเทียบเท่ากับข้อยกเว้นที่ตัวดัดแปลงการเข้าถึงเริ่มต้นสำหรับคลาสนั้น "ส่วนตัว" ในขณะที่มันเป็นสาธารณะสำหรับ structs มีความแตกต่างเล็ก ๆ อื่น ๆ ที่คุณสามารถเรียนรู้ได้โดยดูที่คำถามนี้
Luc Touraille

3
ฉันได้เพิ่มประโยคในตอนเริ่มต้นของคำตอบนี้เพื่อชี้แจงว่าคำถามนั้นตั้งอยู่บนสมมติฐานที่ผิด หากมีคนถามว่า "ทำไม X ถึงเป็นจริง" เมื่อในความเป็นจริง X ไม่เป็นความจริงเราควรปฏิเสธสมมติฐานอย่างรวดเร็ว
Aaron McDaid

250

คำตอบที่ถูกต้องมากมายที่นี่ แต่ฉันต้องการเพิ่ม (เพื่อความสมบูรณ์):

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

แก้ไข: การเพิ่มตัวอย่างการสร้างอินสแตนซ์เทมเพลตอย่างชัดเจน ใช้หลังจากที่มีการกำหนดแม่แบบและฟังก์ชั่นสมาชิกทั้งหมดได้รับการกำหนด

template class vector<int>;

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

ตัวอย่างข้างต้นค่อนข้างไร้ประโยชน์เนื่องจากเวกเตอร์ถูกกำหนดไว้ในส่วนหัวยกเว้นเมื่อไฟล์รวมทั่วไป (ส่วนหัวที่คอมไพล์แล้ว?) ใช้extern template class vector<int>เพื่อป้องกันไม่ให้อินสแตนซ์นั้นในไฟล์อื่น ๆ (1000?) ที่ใช้เวกเตอร์


51
ฮึ. คำตอบที่ดี แต่ไม่มีทางออกที่สะอาดจริง การแสดงรายการประเภทที่เป็นไปได้ทั้งหมดสำหรับเทมเพลตดูเหมือนจะไม่สอดคล้องกับสิ่งที่แม่แบบนั้นควรจะเป็น
Jiminion

6
สิ่งนี้สามารถทำได้ดีในหลายกรณี แต่โดยทั่วไปแล้ววัตถุประสงค์ของเทมเพลตซึ่งมีวัตถุประสงค์เพื่อให้คุณสามารถใช้คลาสได้typeโดยไม่ต้องแสดงรายการด้วยตนเอง
Tomáš Zato - Reinstate Monica

7
vectorไม่ใช่ตัวอย่างที่ดีเนื่องจากคอนเทนเนอร์มีการกำหนดเป้าหมายตามประเภท "ทั้งหมด" โดยเนื้อแท้ แต่เกิดขึ้นบ่อยครั้งมากที่คุณสร้างเทมเพลตที่มีความหมายเฉพาะสำหรับชุดประเภทที่เฉพาะเจาะจงเช่นประเภทตัวเลข: int8_t, int16_t, int32_t, uint8_t, uint16_t เป็นต้นในกรณีนี้คุณควรใช้เทมเพลต แต่การสร้างอินสแตนซ์ให้กับประเภททั้งหมดอย่างชัดเจนนั้นเป็นไปได้และในความคิดของฉันก็แนะนำ
UncleZeiv

ใช้หลังจากที่มีการกำหนดแม่แบบ "และฟังก์ชั่นสมาชิกทั้งหมดได้รับการกำหนด" ขอบคุณมาก!
Vitt Volt

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

250

เป็นเพราะความต้องการในการรวบรวมแยกต่างหากและเนื่องจากแม่แบบนั้นมีความหลากหลายในรูปแบบการสร้างอินสแตนซ์

ให้คำอธิบายใกล้เคียงกับคอนกรีตเล็กน้อย ว่าฉันมีไฟล์ต่อไปนี้:

  • foo.h
    • ประกาศอินเตอร์เฟสของ class MyClass<T>
  • foo.cpp
    • กำหนดการดำเนินการของ class MyClass<T>
  • bar.cpp
    • การใช้งาน 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คิดที่จะให้

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

  • baz.cpp
    • ประกาศและดำเนินการclass BazPrivateและใช้งานMyClass<BazPrivate>

ไม่มีทางที่เป็นไปได้ที่สิ่งนี้จะสามารถทำงานได้ถ้าเราไม่

  1. ต้องคอมไพล์foo.cppทุกครั้งที่เราเปลี่ยนไฟล์อื่น ๆ ในโปรแกรมในกรณีที่มันเพิ่ม instantiation ใหม่ของMyClass<T>
  2. จำเป็นต้องให้baz.cppมี (อาจรวมถึงผ่านทางส่วนหัว) แม่แบบเต็มรูปแบบของMyClass<T>เพื่อให้คอมไพเลอร์สามารถสร้างMyClass<BazPrivate>ในระหว่างการรวบรวมของbaz.cpp

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


50
การเน้นคำพูดของแม่แบบนั้นแท้จริงแล้วเป็นแม่แบบ เทมเพลตของชั้นเรียนไม่ใช่คลาส แต่เป็นสูตรสำหรับสร้างคลาสใหม่สำหรับแต่ละ T ที่เราพบ
v.oddou

ฉันต้องการทราบว่าเป็นไปได้ไหมที่จะทำอินสแตนซ์ชัดเจนจากที่อื่นนอกเหนือจากส่วนหัวของคลาสหรือไฟล์ต้นฉบับ ตัวอย่างเช่นทำใน main.cpp หรือไม่
gromit190

1
@Birger คุณควรจะสามารถทำได้จากไฟล์ใด ๆ ที่มีการเข้าถึงการใช้งานเทมเพลตเต็มรูปแบบ (เพราะมันอยู่ในไฟล์เดียวกันหรือรวมถึงส่วนหัวด้วย)
Ben

11
@ajeh มันไม่ใช่สำนวน คำถามคือ "ทำไมคุณต้องใช้เทมเพลตในส่วนหัว?" ดังนั้นฉันจึงอธิบายทางเลือกด้านเทคนิคที่ภาษา C ++ ทำให้เกิดความต้องการนี้ ก่อนที่ฉันจะเขียนคำตอบผู้อื่นได้เตรียมวิธีแก้ไขปัญหาที่ไม่ใช่วิธีแก้ปัญหาแบบเต็มแล้วเพราะไม่มีวิธีแก้ปัญหาที่สมบูรณ์ ฉันรู้สึกว่าคำตอบเหล่านั้นจะได้รับการเสริมด้วยการอภิปรายที่เต็มไปด้วยมุมมอง "ทำไม" ของคำถาม
เบ็น

1
ลองคิดดูว่าวิธีนี้จะทำให้ผู้ใช้ ... หากคุณไม่ได้ใช้เทมเพลต (เพื่อเขียนโค้ดที่มีประสิทธิภาพตามที่คุณต้องการ) คุณจะเสนอคลาสนั้นเพียงไม่กี่รุ่นเท่านั้น ดังนั้นคุณมี 3 ตัวเลือก 1) อย่าใช้เทมเพลต (เช่นคลาส / ฟังก์ชั่นอื่น ๆ ไม่มีใครใส่ใจว่าคนอื่นไม่สามารถเปลี่ยนประเภทได้) 2) ใช้เทมเพลตและเอกสารที่สามารถใช้ได้ 3) ให้โบนัสแก่พวกเขา (แหล่งที่มา) ดำเนินการ 4 ให้พวกเขามีที่มาทั้งในกรณีที่พวกเขาต้องการที่จะทำให้แม่แบบจากหนึ่งในชั้นเรียนของคุณอีก;)
บ่อ

81

เทมเพลตจำเป็นต้องมีอินสแตนซ์ของคอมไพเลอร์ก่อนรวบรวมเป็นโค้ด การสร้างอินสแตนซ์นี้สามารถทำได้ก็ต่อเมื่อรู้ข้อโต้แย้งของเทมเพลต ตอนนี้คิดว่าสถานการณ์ที่ฟังก์ชั่นแม่แบบจะมีการประกาศในa.hที่กำหนดไว้ในและนำมาใช้ในa.cpp b.cppเมื่อa.cppรวบรวมแล้วไม่จำเป็นว่าการรวบรวมที่จะเกิดขึ้นb.cppจะต้องมีอินสแตนซ์ของเทมเพลต สำหรับไฟล์ส่วนหัวและแหล่งที่มาเพิ่มเติมสถานการณ์อาจซับซ้อนขึ้นอย่างรวดเร็ว

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


1
"การส่งออก" เป็นมาตรฐาน แต่เป็นเรื่องยากที่จะนำไปปฏิบัติดังนั้นทีมคอมไพเลอร์ส่วนใหญ่ก็ยังไม่ได้ทำ
vava

5
การส่งออกไม่ได้ลดความจำเป็นในการเปิดเผยข้อมูลและไม่ลดการพึ่งพาการคอมไพล์ในขณะที่ต้องใช้ความพยายามอย่างมากจากผู้สร้างคอมไพเลอร์ ดังนั้น Herb Sutter จึงขอให้ผู้สร้างคอมไพเลอร์ส่งออก 'ลืม' เป็นเงินลงทุนเวลาที่จำเป็นจะดีกว่าที่จะใช้จ่ายอื่น ๆ ...
ปีเตอร์

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

3
หากคุณสนใจสิ่งนั้นกระดาษที่เรียกว่า "ทำไมเราถึงส่งออกไม่ได้" มันอยู่ในบล็อกของเขา ( gotw.ca/publications ) แต่ไม่มีไฟล์ PDF ที่นั่น (google ฉบับย่อควรเปิดใช้งานได้)
Pieter

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

62

ที่จริงก่อน C ++ 11 มาตรฐานกำหนดexportคำสำคัญที่จะทำให้สามารถประกาศแม่แบบในไฟล์ส่วนหัวและนำไปใช้ที่อื่น

ไม่มีคอมไพเลอร์ยอดนิยมที่ใช้คำค้นหานี้ สิ่งเดียวที่ฉันรู้คือส่วนหน้าเขียนโดย Edison Design Group ซึ่งใช้โดยคอมไพเลอร์ Comeau C ++ ผู้อื่นทั้งหมดต้องการให้คุณเขียนเทมเพลตในไฟล์ส่วนหัวเนื่องจากคอมไพเลอร์ต้องการคำนิยามเทมเพลตสำหรับการสร้างอินสแตนซ์ที่เหมาะสม

ด้วยเหตุนี้คณะกรรมการมาตรฐาน ISO C ++ จึงตัดสินใจที่จะลบexportคุณลักษณะของเทมเพลตด้วย C ++ 11


6
... และสองสามปีต่อมาในที่สุดฉันก็เข้าใจสิ่งที่exportจะให้เราจริง ๆและสิ่งที่ไม่ ... และตอนนี้ฉันเห็นด้วยอย่างสุดซึ้งกับคน EDG: มันจะไม่ทำให้สิ่งที่คนส่วนใหญ่ (ตัวเองใน '11 รวมแล้ว) คิดว่าจะทำได้และมาตรฐาน C ++ จะดีกว่าหากไม่มี
DevSolar

4
@DevSolar: บทความนี้เป็นเรื่องการเมืองซ้ำซากและเขียนไม่ดี นั่นไม่ใช่ระดับมาตรฐานปกติที่ร้อยแก้วที่นั่น ยาวและน่าเบื่ออย่างไม่น่าเชื่อโดยทั่วไปจะพูดถึงสิ่งเดียวกัน 3 ครั้งบนหน้ากระดาษนับสิบหน้า แต่ตอนนี้ฉันได้รับแจ้งว่าการส่งออกไม่ใช่การส่งออก นั่นเป็น intel ที่ดี!
v.oddou

1
@ v.oddou: นักพัฒนาที่ดีและนักเขียนด้านเทคนิคที่ดีเป็นสองทักษะแยกกัน บางคนสามารถทำทั้งสองอย่างหลายคนทำไม่ได้ ;-)
DevSolar

@ v.oddou กระดาษไม่ได้เป็นเพียงแค่เขียนไม่ดี แต่มันบิดเบือนข้อมูล นอกจากนี้ยังเป็นการหมุนในความเป็นจริง: อะไรคือข้อโต้แย้งที่แข็งแกร่งอย่างยิ่งสำหรับการส่งออกที่ถูกผสมในลักษณะที่จะทำให้ดูเหมือนว่าพวกเขาต่อต้านการส่งออก:“ การค้นพบหลุม ODR ที่เกี่ยวข้องจำนวนมากในมาตรฐานเมื่อมีการส่งออก ก่อนที่จะส่งออกการละเมิด ODR ไม่จำเป็นต้องได้รับการวินิจฉัยโดยคอมไพเลอร์ ตอนนี้มันเป็นสิ่งจำเป็นเพราะคุณต้องรวมโครงสร้างข้อมูลภายในจากหน่วยการแปลที่แตกต่างกันและคุณไม่สามารถรวมพวกมันได้หากพวกเขาเป็นตัวแทนของสิ่งต่าง ๆ ดังนั้นคุณต้องทำการตรวจสอบ”
curiousguy

" ต้องเพิ่มหน่วยการแปลที่อยู่ในตอนที่เกิดขึ้น " Duh เมื่อคุณถูกบังคับให้ใช้อาร์กิวเมนต์ที่อ่อนแอคุณไม่มีข้อโต้แย้งใด ๆ แน่นอนคุณกำลังจะพูดถึงชื่อไฟล์ในข้อผิดพลาดของคุณจัดการคืออะไร? ใครก็ตามที่ตกหลุมรัก BS นั้นก็สามารถเชื่อได้ " แม้แต่ผู้เชี่ยวชาญอย่าง James Kanze ก็ยังยากที่จะยอมรับว่าการส่งออกนั้นเป็นเช่นนี้จริง ๆ " อะไรนะ !!!!
curiousguy

34

แม้ว่า C ++ มาตรฐานจะไม่มีข้อกำหนดดังกล่าวคอมไพเลอร์บางตัวจำเป็นต้องใช้ฟังก์ชั่นและแม่แบบคลาสทั้งหมดเพื่อให้พร้อมใช้งานในทุกหน่วยการแปลที่ใช้ ผลสำหรับคอมไพเลอร์เหล่านั้นเนื้อความของฟังก์ชันเท็มเพลตต้องทำให้พร้อมใช้งานในไฟล์ส่วนหัว ในการทำซ้ำ: นั่นหมายถึงคอมไพเลอร์เหล่านั้นจะไม่อนุญาตให้กำหนดไว้ในไฟล์ที่ไม่ใช่ส่วนหัวเช่นไฟล์. cpp

มีคำสำคัญการส่งออกซึ่งควรจะบรรเทาปัญหานี้ แต่ก็ไม่มีที่ไหนใกล้เคียงกับการพกพา


เหตุใดฉันจึงไม่สามารถใช้งานได้ในไฟล์. cpp ด้วยคำหลัก "inline"
MainID

2
คุณทำได้และคุณไม่จำเป็นต้องใส่ "inline" แต่คุณจะสามารถใช้งานได้ในไฟล์ cpp นั้นและไม่มีที่อื่นอีกแล้ว
vava

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

28

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

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

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

ปัญหาการรวบรวมแยกสำหรับเทมเพลตฉันคิดว่ามันเป็นปัญหาที่เกิดขึ้นกับการย้ายข้อมูลไปยังโมดูลซึ่งกำลังทำงานอยู่


15

หมายความว่าวิธีแบบพกพาที่สุดในการกำหนดการใช้งานเมธอดของคลาสเทมเพลตคือการกำหนดไว้ในนิยามคลาสเทมเพลต

template < typename ... >
class MyClass
{

    int myMethod()
    {
       // Not just declaration. Add method implementation here
    }
};

15

แม้ว่าจะมีคำอธิบายที่ดีมากมายอยู่ด้านบน แต่ฉันก็ขาดวิธีที่ใช้งานได้จริงในการแยกแม่แบบออกเป็นส่วนหัวและส่วนร่าง
ความกังวลหลักของฉันคือการหลีกเลี่ยงการคอมไพล์ใหม่ของผู้ใช้เทมเพลตทั้งหมดเมื่อฉันเปลี่ยนคำจำกัดความ
การมีอินสแตนซ์เทมเพลตทั้งหมดในเทมเพลตเทมเพลตไม่ใช่วิธีแก้ปัญหาสำหรับฉันเนื่องจากผู้เขียนเทมเพลตอาจไม่ทราบทั้งหมดหากการใช้งานและผู้ใช้เทมเพลตอาจไม่มีสิทธิ์ในการแก้ไข
ฉันใช้วิธีการต่อไปนี้ซึ่งใช้ได้กับคอมไพเลอร์รุ่นเก่า (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;
}

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


1
ฉันชอบวิธีการนี้ยกเว้นMyInstantiatedTemplate.hไฟล์และเพิ่มMyInstantiatedTemplateประเภท มันค่อนข้างสะอาดกว่าถ้าคุณไม่ใช้มัน ชำระเงินคำตอบของฉันสำหรับคำถามอื่นที่แสดงสิ่งนี้: stackoverflow.com/a/41292751/4612476
Cameron Tacklind

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

8

เพียงเพิ่มบางสิ่งที่สำคัญที่นี่ หนึ่งสามารถกำหนดวิธีการของคลาส 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;

    ...
}

2
สำหรับผู้ชายจริง ๆ ถ้าเป็นจริงคุณควรตรวจสอบคำตอบของคุณให้ถูกต้องเพราะเหตุใดใครจึงต้องการสิ่ง Voodo ที่แฮ็คทั้งหมดหากคุณสามารถกำหนดวิธีการที่ไม่ใช่สมาชิกเทมเพลตใน. cpp ได้?
Michael IV

นั่นไม่ได้ผลอย่างน้อยใน MSVC 2019 รับสัญลักษณ์ภายนอกที่ไม่ได้รับการแก้ไขสำหรับฟังก์ชันสมาชิกของคลาสเทมเพลต
Michael IV

ฉันไม่มี MSVC 2019 ในการทดสอบ สิ่งนี้ได้รับอนุญาตจากมาตรฐาน C ++ ตอนนี้ MSVC มีชื่อเสียงในการไม่ปฏิบัติตามกฎเสมอไป หากคุณยังไม่ได้ลองใช้การตั้งค่าโครงการ -> C / C ++ -> ภาษา -> โหมดความสอดคล้อง -> ใช่ (อนุญาต ())
Nikos

1
ตัวอย่างที่แน่นอนนี้ใช้งานได้ แต่คุณไม่สามารถโทรisEmptyจากหน่วยการแปลอื่นนอกเหนือจากmyQueue.cpp...
MM

7

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


2
การตอบสนองนี้ควรจะเพิ่มมากขึ้น ฉัน " อิสระ " ค้นพบวิธีการเดียวกันของคุณและกำลังมองหาคนอื่นที่จะใช้มันแล้วโดยเฉพาะเนื่องจากฉันอยากรู้ว่ามันเป็นรูปแบบที่เป็นทางการและไม่ว่าจะมีชื่อ แนวทางของฉันคือการนำไปใช้class XBaseทุกที่ที่ฉันต้องนำไปใช้template class Xวางชิ้นส่วนที่ขึ้นกับชนิดลงในXและส่วนที่เหลือXBaseทั้งหมด
Fabio A.

6

นั่นถูกต้องเพราะคอมไพเลอร์ต้องทราบว่าเป็นประเภทใดสำหรับการจัดสรร ดังนั้นคลาสเทมเพลต, ฟังก์ชั่น, enums ฯลฯ จะต้องดำเนินการเช่นกันในไฟล์ส่วนหัวถ้ามันจะทำให้เป็นสาธารณะหรือเป็นส่วนหนึ่งของห้องสมุด (คงที่หรือแบบไดนามิก) เพราะไฟล์ส่วนหัวจะไม่รวบรวมเหมือนไฟล์ c / cpp เป็น หากคอมไพเลอร์ไม่ทราบว่าประเภทนั้นไม่สามารถรวบรวมได้ ใน. Net มันสามารถเพราะวัตถุทั้งหมดได้มาจากชั้นวัตถุ นี่ไม่ใช่. Net


5
"ไฟล์ส่วนหัวไม่ได้รับการรวบรวม" - นั่นเป็นวิธีแปลก ๆ ในการอธิบาย ไฟล์ส่วนหัวสามารถเป็นส่วนหนึ่งของหน่วยการแปลเช่นเดียวกับไฟล์ "c / cpp"
เฟล็กโซ

2
ในความเป็นจริงมันเกือบจะตรงกันข้ามกับความจริงซึ่งก็คือไฟล์ส่วนหัวจะถูกรวบรวมบ่อยครั้งมากในขณะที่ไฟล์ต้นฉบับมักจะรวบรวมครั้งเดียว
xaxxon

6

คอมไพเลอร์จะสร้างรหัสสำหรับแต่ละอินสแตนซ์ของอินสแตนซ์เมื่อคุณใช้เทมเพลตในระหว่างขั้นตอนการรวบรวม ในการรวบรวมและเชื่อมโยงไฟล์. 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 แต่ไม่ใช่ว่าคอมไพเลอร์จำนวนมากจะใช้สิ่งนี้

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


4

วิธีที่จะมีการใช้งานแยกเป็นดังนี้

//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 หรืออื่น ๆ


2
s / inner_foo / foo / g และรวม foo.tpp ที่ส่วนท้ายของ foo.h ไฟล์เดียวที่น้อยกว่า

1

ฉันขอแนะนำให้ดูหน้า 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

ซึ่งกำลังได้รับการสนับสนุนในคอมไพเลอร์จำนวนมาก อย่างไรก็ตามฉันคิดว่าส่วนหัวที่คอมไพล์แล้วจะยากสำหรับไฟล์ส่วนหัวเทมเพลต


-2

อีกเหตุผลหนึ่งที่ควรเขียนทั้งการประกาศและคำจำกัดความในไฟล์ส่วนหัวนั้นเพื่อความสะดวกในการอ่าน สมมติว่ามีฟังก์ชันเทมเพลตอยู่ใน 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 นี้ในคลาสของคุณเองแม้ว่าคอมไพเลอร์จะบอกคุณในสิ่งนี้ กรณีที่ผู้ประกอบการที่จะต้องถูกแทนที่


-7

คุณสามารถกำหนดคลาสเทมเพลตของคุณภายในไฟล์. 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];
    }

2
คนส่วนใหญ่จะกำหนดไฟล์ส่วนหัวให้เป็นสิ่งที่เผยแพร่คำจำกัดความไปยังไฟล์ต้นฉบับ ดังนั้นคุณอาจตัดสินใจใช้นามสกุลไฟล์ ".template" แต่คุณได้เขียนไฟล์ส่วนหัว
ทอมมี่
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.