การแบ่งคลาส C ++ แบบเทมเพลเป็นไฟล์. hpp / .cpp - เป็นไปได้ไหม


99

ฉันได้รับข้อผิดพลาดในการพยายามรวบรวมคลาสเทมเพลต C ++ ซึ่งแยกระหว่าง a .hppและ.cppไฟล์:

$ g++ -c -o main.o main.cpp  
$ g++ -c -o stack.o stack.cpp   
$ g++ -o main main.o stack.o  
main.o: In function `main':  
main.cpp:(.text+0xe): undefined reference to 'stack<int>::stack()'  
main.cpp:(.text+0x1c): undefined reference to 'stack<int>::~stack()'  
collect2: ld returned 1 exit status  
make: *** [program] Error 1  

นี่คือรหัสของฉัน:

stack.hpp :

#ifndef _STACK_HPP
#define _STACK_HPP

template <typename Type>
class stack {
    public:
            stack();
            ~stack();
};
#endif

stack.cpp :

#include <iostream>
#include "stack.hpp"

template <typename Type> stack<Type>::stack() {
        std::cerr << "Hello, stack " << this << "!" << std::endl;
}

template <typename Type> stack<Type>::~stack() {
        std::cerr << "Goodbye, stack " << this << "." << std::endl;
}

main.cpp :

#include "stack.hpp"

int main() {
    stack<int> s;

    return 0;
}

ldเป็นหลักสูตรที่ถูกต้อง: stack.oสัญลักษณ์ไม่ได้อยู่ใน

คำตอบสำหรับคำถามนี้ไม่ได้ช่วยอะไรเพราะฉันทำตามที่พูดไปแล้ว
วิธีนี้อาจช่วยได้ แต่ฉันไม่ต้องการย้ายทุกวิธีลงใน.hppไฟล์ - ฉันไม่ควรทำควรทำอย่างไร

เป็นทางออกเดียวที่สมเหตุสมผลในการย้ายทุกอย่างใน.cppไฟล์ไปยัง.hppไฟล์และรวมทุกอย่างแทนที่จะเชื่อมโยงเป็นไฟล์ออบเจ็กต์แบบสแตนด์อโลนหรือไม่ มันดูน่าเกลียดชะมัด ! ในกรณีนั้นฉันอาจเปลี่ยนกลับไปเป็นสถานะก่อนหน้าและเปลี่ยนชื่อstack.cppเป็นstack.hppและดำเนินการได้


มีวิธีแก้ปัญหาที่ยอดเยี่ยมสองวิธีเมื่อคุณต้องการซ่อนโค้ดของคุณไว้ (ในไฟล์ไบนารี) หรือทำให้มันสะอาด จำเป็นต้องลดความทั่วไปแม้ว่าในสถานการณ์แรก มีอธิบายไว้ที่นี่: stackoverflow.com/questions/495021/…
Sheric

การสร้างอินสแตนซ์เทมเพลตที่ชัดเจนคือวิธีลดเวลาการคอมไพล์ของเทมเพลต: stackoverflow.com/questions/2351148/…
Ciro Santilli 郝海东冠状病六四事件法轮功

คำตอบ:


152

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

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

ที่จริงต้องเข้าใจว่าคลาสเทมเพลตไม่ใช่คลาสเลย แต่เป็นเทมเพลตสำหรับคลาสที่การประกาศและคำจำกัดความซึ่งสร้างขึ้นโดยคอมไพเลอร์ในเวลาคอมไพล์หลังจากได้รับข้อมูลประเภทข้อมูลจากอาร์กิวเมนต์ ตราบใดที่ไม่สามารถสร้างเค้าโครงหน่วยความจำได้จะไม่สามารถสร้างคำแนะนำสำหรับนิยามวิธีการได้ จำไว้ว่าอาร์กิวเมนต์แรกของ class method คือตัวดำเนินการ "this" เมธอดคลาสทั้งหมดจะถูกแปลงเป็นแต่ละเมธอดโดยมีชื่อ mangling และพารามิเตอร์แรกเป็นอ็อบเจกต์ที่ทำงาน อาร์กิวเมนต์ 'this' เป็นสิ่งที่บอกเกี่ยวกับขนาดของอ็อบเจ็กต์ซึ่งในกรณีของคลาส template ไม่พร้อมใช้งานสำหรับคอมไพลเลอร์เว้นแต่ว่าผู้ใช้จะสร้างอินสแตนซ์อ็อบเจ็กต์ด้วยอาร์กิวเมนต์ชนิดที่ถูกต้อง ในกรณีนี้ถ้าคุณใส่นิยามเมธอดในไฟล์ cpp แยกต่างหากและพยายามคอมไพล์ไฟล์อ็อบเจ็กต์เองจะไม่ถูกสร้างขึ้นด้วยข้อมูลคลาส การคอมไพล์จะไม่ล้มเหลวมันจะสร้างไฟล์อ็อบเจ็กต์ แต่จะไม่สร้างโค้ดสำหรับคลาสเทมเพลตในอ็อบเจ็กต์ไฟล์ นี่คือสาเหตุที่ตัวเชื่อมโยงไม่พบสัญลักษณ์ในไฟล์อ็อบเจ็กต์และการสร้างล้มเหลว

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

ฉันหวังว่าการสนทนานี้จะเป็นประโยชน์


2
"ต้องเข้าใจว่าคลาสแม่แบบไม่ใช่คลาสเลย" - มันเป็นอย่างอื่นไม่ใช่เหรอ เทมเพลตคลาสคือเทมเพลต บางครั้ง "เทมเพลทคลาส" ถูกใช้แทน "อินสแตนซ์ของเทมเพลต" และจะเป็นคลาสจริง
Xupicor

สำหรับการอ้างอิงการบอกว่าไม่มีวิธีแก้ปัญหาก็ไม่ถูกต้อง! การแยกโครงสร้างข้อมูลออกจากวิธีการก็เป็นความคิดที่ไม่ดีเช่นกันเนื่องจากถูกต่อต้านโดยการห่อหุ้ม มีวิธีแก้ปัญหาที่ยอดเยี่ยมที่คุณสามารถใช้ได้ในบางสถานการณ์ (ฉันเชื่อว่าส่วนใหญ่) ที่นี่: stackoverflow.com/questions/495021/…
Sheric

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

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

สิ่งที่ใกล้เคียงที่สุดที่ฉันเพิ่งพบในการทำงานนี้คือการใช้ไฟล์. h / .hpp คู่หนึ่งและ #include "filename.hpp" ที่ท้ายไฟล์. h ที่กำหนดคลาสเทมเพลตของคุณ (ด้านล่างวงเล็บปีกกาปิดของคุณสำหรับนิยามคลาสด้วยอัฒภาค) อย่างน้อยสิ่งนี้จะแยกพวกมันในเชิงโครงสร้างแบบไฟล์และได้รับอนุญาตเนื่องจากท้ายที่สุดคอมไพเลอร์คัดลอก / วางรหัส. hpp ของคุณทับ #include "filename.hpp" ของคุณ
Artorias2718

90

มันเป็นไปได้ตราบใดที่คุณรู้ว่าสิ่งที่ instantiations คุณจะต้อง

เพิ่มรหัสต่อไปนี้ที่ส่วนท้ายของ stack.cpp และใช้งานได้:

template class stack<int>;

เมธอดที่ไม่ใช่เทมเพลตทั้งหมดของสแต็กจะถูกสร้างอินสแตนซ์และขั้นตอนการเชื่อมโยงจะทำงานได้ดี


7
ในทางปฏิบัติคนส่วนใหญ่ใช้ไฟล์ cpp แยกต่างหากสำหรับสิ่งนี้เช่น stackinstantiation.cpp
Nemanja Trifunovic

@NemanjaTrifunovic คุณช่วยยกตัวอย่างได้ไหมว่า stackinstantiation.cpp จะเป็นอย่างไร?
qwerty9967

3
จริงๆมีวิธีแก้ปัญหาอื่น ๆ : codeproject.com/Articles/48575/…
sleepsort

@ Benoîtฉันได้รับข้อผิดพลาด error: คาดว่า unqualified-id ก่อน ';' โทเค็นเท็มเพลตกอง <int>; คุณรู้ไหมว่าทำไม? ขอบคุณ!
camino

3
template class stack<int>;ที่จริงไวยากรณ์ที่ถูกต้องคือ
Paul Baltescu

8

คุณสามารถทำได้ด้วยวิธีนี้

// xyz.h
#ifndef _XYZ_
#define _XYZ_

template <typename XYZTYPE>
class XYZ {
  //Class members declaration
};

#include "xyz.cpp"
#endif

//xyz.cpp
#ifdef _XYZ_
//Class definition goes here

#endif

มีการพูดคุยเรื่องนี้ในDaniweb

นอกจากนี้ในคำถามที่พบบ่อยแต่ใช้คำสำคัญการส่งออก C ++


5
includecppโดยทั่วไปการเข้าไฟล์เป็นความคิดที่แย่มาก แม้ว่าคุณจะมีเหตุผลที่ถูกต้องสำหรับเรื่องนี้ไฟล์ซึ่งเป็นเพียงส่วนหัวที่ได้รับการยกย่อง - ควรได้รับhppส่วนขยายหรือส่วนขยายที่แตกต่างกัน (เช่นtpp) เพื่อให้ชัดเจนว่าเกิดอะไรขึ้นขจัดความสับสนเกี่ยวmakefileกับการกำหนดเป้าหมายไฟล์จริง cppฯลฯ
underscore_d

@underscore_d คุณช่วยอธิบายได้ไหมว่าทำไมการรวม.cppไฟล์จึงเป็นความคิดที่แย่มาก
Abbas

1
@Abbas เนื่องจากส่วนขยายcpp(หรือccหรือcหรืออะไรก็ตาม) บ่งชี้ว่าไฟล์เป็นส่วนหนึ่งของการนำไปใช้งานหน่วยการแปลที่เป็นผลลัพธ์ (เอาต์พุตของตัวประมวลผลก่อนหน้า) สามารถคอมไพล์แยกกันได้และเนื้อหาของไฟล์จะถูกคอมไพล์เพียงครั้งเดียวเท่านั้น ไม่ได้ระบุว่าไฟล์เป็นส่วนที่นำกลับมาใช้ใหม่ได้ของอินเทอร์เฟซที่จะรวมไว้ที่ใดก็ได้โดยพลการ #includeการติดตั้งไฟล์จริง cppจะทำให้หน้าจอของคุณเต็มไปด้วยข้อผิดพลาดของข้อกำหนดหลายประการและถูกต้อง ในกรณีนี้เป็นมีเป็นเหตุผลที่ไป#includeมันcppเป็นเพียงทางเลือกที่ผิดของการขยาย
underscore_d

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

1
@Abbas ใช่cpp/ cc/ ฯลฯ จะต้องหลีกเลี่ยง แต่มันเป็นความคิดที่ดีที่จะใช้สิ่งอื่นที่ไม่ใช่hpp- เช่นtpp, tccฯลฯ - เพื่อให้คุณสามารถนำมาใช้ส่วนที่เหลือของชื่อไฟล์และระบุว่าtppไฟล์แม้ว่ามันจะทำหน้าที่เหมือนส่วนหัว hppถือการดำเนินงานที่ออกจากสายของการประกาศแม่แบบในการที่สอดคล้องกัน ดังนั้นโพสต์นี้จึงเริ่มต้นด้วยหลักฐานที่ดี - แยกการประกาศและคำจำกัดความออกเป็น 2 ไฟล์ที่แตกต่างกันซึ่งอาจง่ายกว่าที่จะ grok / grep หรือบางครั้งก็จำเป็นเนื่องจากการอ้างอิงแบบวงกลม IME - แต่ก็จบลงด้วยการแนะนำว่าไฟล์ที่ 2 มีนามสกุลที่ไม่ถูกต้อง
underscore_d

6

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

สิ่งที่ดีที่สุดที่คุณทำได้คือวางการใช้งานฟังก์ชันในไฟล์ ".tcc" หรือ ".tpp" และ # รวมไฟล์. tcc ไว้ท้ายไฟล์. hpp อย่างไรก็ตามนี่เป็นเพียงเครื่องสำอาง ยังคงเหมือนกับการใช้งานทุกอย่างในไฟล์ส่วนหัว นี่เป็นเพียงราคาที่คุณจ่ายสำหรับการใช้เทมเพลต


3
คำตอบของคุณไม่ถูกต้อง คุณสามารถสร้างโค้ดจากคลาสเทมเพลตในไฟล์ cpp โดยให้คุณทราบว่าจะใช้อาร์กิวเมนต์เทมเพลตใด ดูคำตอบของฉันสำหรับข้อมูลเพิ่มเติม
Benoît

2
จริง แต่สิ่งนี้มาพร้อมกับข้อ จำกัด ที่ร้ายแรงในการต้องอัปเดตไฟล์. cpp และคอมไพล์ใหม่ทุกครั้งที่มีการแนะนำประเภทใหม่ซึ่งใช้เทมเพลตซึ่งอาจไม่ใช่สิ่งที่ OP มีอยู่ในใจ
Charles Salvia

3

เฉพาะในกรณีที่คุณ#include "stack.cppอยู่ในตอนท้ายของstack.hpp. ฉันขอแนะนำแนวทางนี้เฉพาะในกรณีที่การใช้งานมีขนาดค่อนข้างใหญ่และหากคุณเปลี่ยนชื่อไฟล์. cpp เป็นนามสกุลอื่นเพื่อแยกความแตกต่างจากรหัสปกติ


4
ถ้าจะทำก็ต้องเพิ่ม #ifndef STACK_CPP (และเพื่อน) ลงในไฟล์ stack.cpp
Stephen Newell

เอาชนะข้อเสนอแนะนี้ให้ฉัน ฉันไม่ชอบแนวทางนี้ด้วยเหตุผลด้านสไตล์
ลูกา

2
ใช่ในกรณีเช่นนี้ไฟล์ที่ 2 ไม่ควรให้นามสกุลcpp( ccหรืออะไรก็ได้) อย่างแน่นอนเพราะมันตรงกันข้ามกับบทบาทที่แท้จริง ควรได้รับส่วนขยายอื่นแทนซึ่งระบุว่าเป็น (A) ส่วนหัวและ (B) ส่วนหัวที่จะรวมไว้ที่ด้านล่างของส่วนหัวอื่น ฉันใช้tppสำหรับสิ่งนี้ซึ่งสามารถยืนได้อย่างง่ายดายสำหรับtem plate im plementation (คำจำกัดความนอกบรรทัด) ฉันอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ที่นี่: stackoverflow.com/questions/1724036/…
underscore_d

3

ฉันเชื่อว่ามีสาเหตุหลักสองประการในการพยายามแยกรหัสเทมเพลตเป็นส่วนหัวและ cpp:

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

อื่น ๆ คือการลดเวลาในการรวบรวม

ขณะนี้ฉัน (เช่นเคย) ซอฟต์แวร์จำลองการเข้ารหัสร่วมกับ OpenCL และเราต้องการเก็บโค้ดเพื่อให้สามารถเรียกใช้โดยใช้ประเภท float (cl_float) หรือ double (cl_double) ได้ตามต้องการขึ้นอยู่กับความสามารถของ HW ตอนนี้สิ่งนี้ทำได้โดยใช้ #define REAL ที่จุดเริ่มต้นของโค้ด แต่มันไม่ได้สวยหรูมากนัก การเปลี่ยนความแม่นยำที่ต้องการจำเป็นต้องมีการคอมไพล์แอปพลิเคชันใหม่ เนื่องจากไม่มีประเภทรันไทม์จริงเราจึงต้องอยู่กับสิ่งนี้ในขณะนี้ โชคดีที่เคอร์เนล OpenCL ถูกคอมไพล์รันไทม์และขนาดที่เรียบง่าย (REAL) ช่วยให้เราสามารถปรับเปลี่ยนรันไทม์โค้ดเคอร์เนลได้

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


2

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


+1 - แม้ว่าส่วนใหญ่จะไม่ได้ผลดีนัก (อย่างน้อยก็ไม่บ่อยเท่าที่ฉันต้องการ)
peterchen

2

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

นอกจากนี้ยังเป็นไปได้ที่จะส่งออกสิ่งเหล่านี้ข้าม DLL (!) แต่มันค่อนข้างยากที่จะทำให้ไวยากรณ์ถูกต้อง (ชุดค่าผสมเฉพาะ MS ของ __declspec (dllexport) และคีย์เวิร์ดการส่งออก)

เราเคยใช้มันใน lib math / geom ที่ templated double / float แต่มีโค้ดค่อนข้างมาก (ตอนนั้นฉัน googled ไปรอบ ๆ แต่ไม่มีรหัสนั้นในวันนี้)


2

ปัญหาคือเทมเพลตไม่ได้สร้างคลาสจริงเป็นเพียงเทมเพลตที่บอกวิธีสร้างคลาสให้คอมไพเลอร์ คุณต้องสร้างคลาสที่เป็นรูปธรรม

วิธีที่ง่ายและเป็นธรรมชาติคือใส่เมธอดในไฟล์ส่วนหัว แต่มีอีกวิธีหนึ่ง

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

stack.cpp ใหม่:

#include <iostream>
#include "stack.hpp"
template <typename Type> stack<Type>::stack() {
        std::cerr << "Hello, stack " << this << "!" << std::endl;
}
template <typename Type> stack<Type>::~stack() {
        std::cerr << "Goodbye, stack " << this << "." << std::endl;
}
static void DummyFunc() {
    static stack<int> stack_int;  // generates the constructor and destructor code
    // ... any other method invocations need to go here to produce the method code
}

8
คุณไม่จำเป็นต้องใช้ฟังก์ชัน dummey: ใช้ 'template stack <int>;' สิ่งนี้บังคับให้อินสแตนซ์ของเทมเพลตเข้าสู่หน่วยคอมไพล์ปัจจุบัน มีประโยชน์มากหากคุณกำหนดเทมเพลต แต่ต้องการการนำไปใช้งานเฉพาะบางอย่างใน lib ที่ใช้ร่วมกัน
Martin York

@ มาร์ติน: รวมทุกฟังก์ชั่นของสมาชิกด้วย? มันอัศจรรย์มาก. คุณควรเพิ่มคำแนะนำนี้ในเธรด "คุณลักษณะ C ++ ที่ซ่อนอยู่"
Mark Ransom

@LokiAstari ฉันพบบทความเกี่ยวกับเรื่องนี้เผื่อว่าใครอยากเรียนรู้เพิ่มเติม: cplusplus.com/forum/articles/14272
Andrew Larsson

1

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

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


1

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

ข้อมูลที่เป็นประโยชน์อยู่ที่นี่: https://docs.microsoft.com/en-us/cpp/cpp/explicit-instantiation?view=vs-2019

สำหรับตัวอย่างเดียวกันของคุณ: Stack.hpp

template <class T>
class Stack {

public:
    Stack();
    ~Stack();
    void Push(T val);
    T Pop();
private:
    T val;
};


template class Stack<int>;

stack.cpp

#include <iostream>
#include "Stack.hpp"
using namespace std;

template<class T>
void Stack<T>::Push(T val) {
    cout << "Pushing Value " << endl;
    this->val = val;
}

template<class T>
T Stack<T>::Pop() {
    cout << "Popping Value " << endl;
    return this->val;
}

template <class T> Stack<T>::Stack() {
    cout << "Construct Stack " << this << endl;
}

template <class T> Stack<T>::~Stack() {
    cout << "Destruct Stack " << this << endl;
}

main.cpp

#include <iostream>
using namespace std;

#include "Stack.hpp"

int main() {
    Stack<int> s;
    s.Push(10);
    cout << s.Pop() << endl;
    return 0;
}

เอาท์พุต:

> Construct Stack 000000AAC012F8B4
> Pushing Value
> Popping Value
> 10
> Destruct Stack 000000AAC012F8B4

อย่างไรก็ตามฉันไม่ชอบวิธีนี้อย่างสิ้นเชิงเพราะสิ่งนี้ทำให้แอปพลิเคชันสามารถถ่ายภาพตัวเองด้วยการส่งผ่านประเภทข้อมูลที่ไม่ถูกต้องไปยังคลาสเทมเพลต ตัวอย่างเช่นในฟังก์ชันหลักคุณสามารถส่งผ่านประเภทอื่น ๆ ที่สามารถแปลงเป็น int โดยปริยายเช่น s.Push (1.2); และนั่นก็ไม่ดีในความคิดของฉัน


คำถามเฉพาะการสร้างอินสแตนซ์เทมเพลตที่ชัดเจน: stackoverflow.com/questions/2351148/…
Ciro Santilli 郝海东冠状病六四事件法轮功

0

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


0

ความเป็นไปได้อีกประการหนึ่งคือการทำสิ่งต่างๆเช่น:

#ifndef _STACK_HPP
#define _STACK_HPP

template <typename Type>
class stack {
    public:
            stack();
            ~stack();
};

#include "stack.cpp"  // Note the include.  The inclusion
                      // of stack.h in stack.cpp must be 
                      // removed to avoid a circular include.

#endif

ฉันไม่ชอบคำแนะนำนี้ในเรื่องของรูปแบบ แต่อาจเหมาะกับคุณ


1
ส่วนหัวที่ 2 ที่ได้รับการยกย่องอย่างน้อยควรมีส่วนขยายอื่นนอกเหนือจากcppเพื่อหลีกเลี่ยงความสับสนกับไฟล์ต้นฉบับจริง ข้อเสนอแนะที่พบบ่อย ได้แก่และtpp tcc
underscore_d

0

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


2
นี่เกือบจะเป็นคำตอบแบบลิงก์เท่านั้นและลิงก์นั้นก็ใช้งานไม่ได้
underscore_d

0

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

2) คลาสที่ไม่ใช่เทมเพลตมีตัวแปรทั้งหมดอย่างเป็นรูปธรรมและกำหนดไว้โดยเฉพาะในไฟล์. h และ. cpp ดังนั้นคอมไพลเลอร์จะมีข้อมูลที่ต้องการเกี่ยวกับชนิดข้อมูลทั้งหมดที่ใช้ในคลาสก่อนที่จะคอมไพล์ / แปลการสร้างอ็อบเจ็กต์ / โค้ดเครื่องคลาสเทมเพลตไม่มีข้อมูลเกี่ยวกับชนิดข้อมูลเฉพาะก่อนที่ผู้ใช้คลาสจะสร้างอินสแตนซ์อ็อบเจ็กต์ที่ส่งผ่านข้อมูลที่ต้องการ ชนิด:

        TClass<int> myObj;

3) หลังจากการสร้างอินสแตนซ์นี้เท่านั้นผู้ปฏิบัติตามจะสร้างเวอร์ชันเฉพาะของคลาสเทมเพลตเพื่อให้ตรงกับประเภทข้อมูลที่ส่งผ่าน

4) ดังนั้นไม่สามารถรวบรวม. cpp แยกต่างหากโดยไม่ทราบประเภทข้อมูลเฉพาะของผู้ใช้ ดังนั้นจึงต้องอยู่เป็นซอร์สโค้ดภายใน“ .h” จนกว่าผู้ใช้จะระบุประเภทข้อมูลที่ต้องการจึงสามารถสร้างเป็นประเภทข้อมูลเฉพาะจากนั้นรวบรวม


-3

ฉันกำลังทำงานกับ Visual studio 2010 หากคุณต้องการแบ่งไฟล์ของคุณเป็น. h และ. cpp ให้ใส่ส่วนหัว cpp ของคุณที่ท้ายไฟล์. h

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