ใช้เทมเพลตภายนอก (C ++ 11)


116

รูปที่ 1: เทมเพลตฟังก์ชัน

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){
   //...
}    
//explicit instantation
template void f<T>();

main.cpp

#include "TemplHeader.h"
extern template void f<T>(); //is this correct?
int main() {
    f<char>();
    return 0;
}

นี่เป็นวิธีใช้ที่ถูกต้องextern templateหรือฉันใช้คีย์เวิร์ดนี้เฉพาะกับเทมเพลตคลาสดังรูปที่ 2

รูปที่ 2: เทมเพลตคลาส

TemplHeader.h

template<typename T>
class foo {
    T f();
};

TemplCpp.cpp

template<typename T>
void foo<T>::f() {
    //...
}
//explicit instantation
template class foo<int>;

main.cpp

#include "TemplHeader.h"
extern template class foo<int>();
int main() {
    foo<int> test;
    return 0;
}

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

นอกจากนี้รูปที่ 1 และรูปที่ 2 อาจขยายเป็นโซลูชันที่เทมเพลตอยู่ในไฟล์ส่วนหัวเดียว ในกรณีนี้เราจำเป็นต้องใช้extern templateคีย์เวิร์ดเพื่อหลีกเลี่ยงการสร้างอินสแตนซ์เดียวกันหลายครั้ง นี่เฉพาะคลาสหรือฟังก์ชันด้วยหรือเปล่า?


3
นี่ไม่ใช่การใช้เทมเพลตภายนอกที่ถูกต้องเลย ... นี่ไม่ได้รวบรวม
Dani

คุณช่วยพูดคำถาม (ข้อเดียว) ให้ชัดเจนขึ้นได้ไหม คุณโพสต์รหัสเพื่ออะไร? ฉันไม่เห็นคำถามที่เกี่ยวข้องกับเรื่องนั้น นอกจากนี้extern template class foo<int>();ดูเหมือนความผิดพลาด
sehe

@Dani> มันรวบรวมได้ดีใน Visual Studio 2010 ของฉันยกเว้นข้อความเตือน: คำเตือน 1 คำเตือน C4231: ส่วนขยายที่ไม่เป็นมาตรฐานที่ใช้: 'extern' ก่อนการสร้างอินสแตนซ์แบบชัดแจ้งของเทมเพลต
codekiddy

2
@ คำถามนี้ง่ายมาก: ทำอย่างไรและเมื่อใดควรใช้คำหลักเทมเพลตภายนอก (เทมเพลตภายนอกคือ C ++ 0x อนาคตใหม่ btw) คุณกล่าวว่า "นอกจากนี้คลาสเทมเพลตภายนอก foo <int> () ดูเหมือนจะผิดพลาด" ไม่ไม่ใช่ฉันมีหนังสือ C ++ เล่มใหม่และนั่นก็เป็นตัวอย่างจากหนังสือของฉัน
codekiddy

1
@codekiddy: สตูดิโอวิชวลก็โง่จริงๆ .. ในอันที่สองต้นแบบไม่ตรงกับการใช้งานและแม้ว่าฉันจะแก้ไขว่า 'คาดว่าจะไม่เข้าเงื่อนไข -ID' ใกล้()กับสายภายนอก ทั้งหนังสือและสตูดิโอภาพของคุณผิดลองใช้คอมไพเลอร์ที่สอดคล้องกับมาตรฐานมากขึ้นเช่น g ++ หรือเสียงดังแล้วคุณจะพบปัญหา
Dani

คำตอบ:


181

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

ตัวอย่างเช่น:

// header.h

template<typename T>
void ReallyBigFunction()
{
    // Body
}

// source1.cpp

#include "header.h"
void something1()
{
    ReallyBigFunction<int>();
}

// source2.cpp

#include "header.h"
void something2()
{
    ReallyBigFunction<int>();
}

ซึ่งจะส่งผลให้เกิดไฟล์ออบเจ็กต์ต่อไปนี้:

source1.o
    void something1()
    void ReallyBigFunction<int>()    // Compiled first time

source2.o
    void something2()
    void ReallyBigFunction<int>()    // Compiled second time

หากไฟล์ทั้งสองเชื่อมโยงเข้าด้วยกันระบบvoid ReallyBigFunction<int>()จะทิ้งไฟล์เดียวทำให้เสียเวลาคอมไพล์และขนาดไฟล์ออบเจ็กต์

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

เปลี่ยนsource2.cppเป็น:

// source2.cpp

#include "header.h"
extern template void ReallyBigFunction<int>();
void something2()
{
    ReallyBigFunction<int>();
}

จะส่งผลให้ไฟล์อ็อบเจ็กต์ต่อไปนี้:

source1.o
    void something1()
    void ReallyBigFunction<int>() // compiled just one time

source2.o
    void something2()
    // No ReallyBigFunction<int> here because of the extern

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

สิ่งนี้ควรใช้ภายในโปรเจ็กต์เท่านั้นเช่นในช่วงเวลาที่คุณใช้เทมเพลตvector<int>หลาย ๆ ครั้งคุณควรใช้externในไฟล์ต้นฉบับทั้งหมดยกเว้นไฟล์เดียว

นอกจากนี้ยังใช้กับคลาสและฟังก์ชันแบบหนึ่งและฟังก์ชันสมาชิกเทมเพลต


2
@codekiddy: ฉันไม่รู้ว่า Visual Studio หมายถึงอะไร คุณควรใช้คอมไพเลอร์ที่เข้ากันได้มากกว่านี้หากคุณต้องการให้โค้ด c ++ 11 ส่วนใหญ่ทำงานได้ถูกต้อง
Dani

4
@Dani: คำอธิบายที่ดีที่สุดของเทมเพลตภายนอกที่ฉันอ่านมาจนถึงตอนนี้!
Pietro

90
"ถ้าคุณรู้ว่ามันถูกใช้ในไบนารีเดียวกันที่อื่น" นั่นไม่เพียงพอหรือไม่จำเป็น รหัสของคุณ "มีรูปแบบไม่ถูกต้องไม่จำเป็นต้องมีการวินิจฉัย" คุณไม่ได้รับอนุญาตให้พึ่งพาการสร้างอินสแตนซ์โดยนัยของ TU อื่น (คอมไพเลอร์ได้รับอนุญาตให้ปรับให้เหมาะสมเหมือนกับฟังก์ชันอินไลน์) ต้องจัดเตรียมการสร้างอินสแตนซ์ที่ชัดเจนใน TU อื่น
Johannes Schaub - litb

32
ฉันอยากจะชี้ให้เห็นว่าคำตอบนี้อาจจะผิดและฉันก็โดนมันกัด โชคดีที่ความคิดเห็นของโจฮันเนสมีคะแนนโหวตสูงขึ้นและฉันให้ความสำคัญกับมันมากขึ้นในครั้งนี้ ฉันสามารถสันนิษฐานได้ว่าผู้มีสิทธิเลือกตั้งส่วนใหญ่ในคำถามนี้ไม่ได้ใช้เทมเพลตประเภทนี้ในหน่วยการรวบรวมหลายหน่วย (เช่นที่ฉันทำในวันนี้) ... ส่วนหัวของคุณ! ขอเตือน!
Steven Lu

6
@ JohannesSchaub-litb คุณช่วยอธิบายเพิ่มเติมอีกนิดหรืออาจจะให้คำตอบที่ดีกว่านี้ ฉันไม่แน่ใจว่าเข้าใจคำคัดค้านของคุณหรือไม่
andreee

48

Wikipedia มีคำอธิบายที่ดีที่สุด

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

C ++ 03 มีไวยากรณ์นี้เพื่อบังคับให้คอมไพเลอร์สร้างอินสแตนซ์เทมเพลต:

  template class std::vector<MyClass>;

ตอนนี้ C ++ 11 มีไวยากรณ์นี้:

  extern template class std::vector<MyClass>;

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

คำเตือน: nonstandard extension used...

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

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


tnx สำหรับการตอบกลับของคุณดังนั้นความหมายของ acctualy นี้คืออนาคต "เทมเพลตภายนอก" ทำงานได้อย่างสมบูรณ์สำหรับ VS 2010 และเราสามารถเพิกเฉยต่อคำเตือนได้หรือไม่ (โดยใช้ pragma เพื่อเพิกเฉยต่อข้อความเป็นต้น) และควรเป็นฝั่งที่เทมเพลตไม่ได้สร้างอินสแตนซ์ตามเวลาใน VSC ++ ผู้รวบรวม ขอบคุณ
codekiddy

4
"... ซึ่งบอกให้คอมไพเลอร์ไม่ต้องสร้างอินสแตนซ์เทมเพลตในหน่วยการแปลนี้" ฉันไม่คิดว่านี่เป็นเรื่องจริง วิธีการใด ๆ ที่กำหนดไว้ในนิยามคลาสจะนับเป็นแบบอินไลน์ดังนั้นหากการใช้งาน STL ใช้วิธีอินไลน์สำหรับstd::vector(ค่อนข้างแน่ใจว่าทั้งหมดทำ) externจะไม่มีผลใด ๆ
Andreas Haferburg

ใช่คำตอบนี้ทำให้เข้าใจผิด MSFT doc: "คีย์เวิร์ดภายนอกในความเชี่ยวชาญจะใช้กับฟังก์ชันสมาชิกที่กำหนดไว้ภายนอกเนื้อหาของคลาสเท่านั้นฟังก์ชันที่กำหนดไว้ในการประกาศคลาสถือเป็นฟังก์ชันอินไลน์และจะถูกสร้างอินสแตนซ์เสมอ" คลาส STL ทั้งหมดใน VS (ตรวจสอบล่าสุดคือ 2017) มีเฉพาะเมธอดแบบอินไลน์เท่านั้น
0kcats

สำหรับการประกาศแบบอินไลน์ทั้งหมดไม่ว่าจะเกิดขึ้นที่ใดเสมอ @ 0kcats
sehe

@sehe การอ้างอิงถึง Wiki พร้อมตัวอย่าง std :: vector และการอ้างอิงถึง MSVC ในคำตอบเดียวกันทำให้เชื่อว่าอาจมีประโยชน์ในการใช้ extern std :: vector ใน MSVC ในขณะที่ยังไม่มี ไม่แน่ใจว่านี่เป็นข้อกำหนดของมาตรฐานหรือไม่คอมไพเลอร์อื่น ๆ อาจมีปัญหาเดียวกัน
0kcats

7

extern template จำเป็นก็ต่อเมื่อการประกาศเทมเพลตเสร็จสมบูรณ์

นี่เป็นคำตอบในคำตอบอื่น ๆ แต่ฉันคิดว่าไม่ได้ให้ความสำคัญกับมันมากพอ

สิ่งนี้หมายความว่าในตัวอย่าง OPs extern templateไม่มีผลใด ๆ เนื่องจากคำจำกัดความของเทมเพลตในส่วนหัวไม่สมบูรณ์:

  • void f();: เพียงแค่ประกาศไม่มีร่างกาย
  • class foo: ประกาศวิธีการf()แต่ไม่มีคำจำกัดความ

ดังนั้นฉันขอแนะนำให้ลบextern templateคำจำกัดความในกรณีนั้นออก: คุณจะต้องเพิ่มคำจำกัดความเหล่านี้หากคลาสถูกกำหนดอย่างสมบูรณ์

ตัวอย่างเช่น:

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){}

// Explicit instantiation for char.
template void f<char>();

main.cpp

#include "TemplHeader.h"

// Commented out from OP code, has no effect.
// extern template void f<T>(); //is this correct?

int main() {
    f<char>();
    return 0;
}

รวบรวมและดูสัญลักษณ์ด้วยnm:

g++ -std=c++11 -Wall -Wextra -pedantic -c -o TemplCpp.o TemplCpp.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -c -o Main.o Main.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -o Main.out Main.o TemplCpp.o
echo TemplCpp.o
nm -C TemplCpp.o | grep f
echo Main.o
nm -C Main.o | grep f

เอาท์พุท:

TemplCpp.o
0000000000000000 W void f<char>()
Main.o
                 U void f<char>()

จากนั้นman nmเราจะเห็นว่านั่นUหมายถึงไม่ได้กำหนดดังนั้นคำจำกัดความจึงยังคงอยู่TemplCppตามที่ต้องการเท่านั้น

ทั้งหมดนี้ทำให้เกิดการแลกเปลี่ยนของการประกาศส่วนหัวที่สมบูรณ์:

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

ตัวอย่างเพิ่มเติมของสิ่งเหล่านี้แสดงอยู่ที่: อินสแตนซ์เทมเพลตที่ชัดเจน - ใช้เมื่อใด

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

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

ทดสอบใน Ubuntu 18.04


4

ปัญหาที่ทราบเกี่ยวกับเทมเพลตคือ code bloating ซึ่งเป็นผลมาจากการสร้างนิยามคลาสในแต่ละโมดูลซึ่งเรียกใช้ความเชี่ยวชาญเทมเพลตคลาส เพื่อป้องกันสิ่งนี้เริ่มต้นด้วย C ++ 0x เราสามารถใช้คีย์เวิร์ดextern ที่ด้านหน้าของความเชี่ยวชาญเทมเพลตคลาส

#include <MyClass>
extern template class CMyClass<int>;

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

template class CMyClass<int>;
template class CMyClass<float>;

0

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

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