เทมเพลตฟังก์ชันสมาชิกระดับสามารถเป็นเสมือนได้หรือไม่?


304

ฉันได้ยินมาว่าแม่แบบฟังก์ชันคลาสสมาชิก C ++ ไม่สามารถเป็นเสมือนได้ มันเป็นเรื่องจริงเหรอ?

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


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

คำตอบ:


329

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

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

แต่มีเทคนิคที่มีประสิทธิภาพและน่าสนใจไม่กี่อันเนื่องมาจากการรวมหลายรูปแบบและแม่แบบสะดุดตาเรียกว่าลบออกประเภท


32
ฉันไม่เห็นเหตุผลทางภาษาสำหรับเรื่องนี้เพียงเหตุผลการดำเนินการเท่านั้น vtables ไม่ได้เป็นส่วนหนึ่งของภาษา - เพียงวิธีมาตรฐานในการคอมไพเลอร์ใช้ภาษา
gerardw

16
Virtual functions are all about the run-time system figuring out which function to call at run-time- ขอโทษ แต่นี่เป็นวิธีที่ผิดไปและค่อนข้างสับสน เป็นเพียงการอ้อมและไม่มี "การหารันไทม์" ที่เกี่ยวข้องมันเป็นที่รู้จักกันในช่วงเวลาที่รวบรวมฟังก์ชั่นที่จะเรียกว่าเป็นสิ่งที่ชี้ไปโดยตัวชี้ n-th ใน vtable "การหา" หมายความว่ามีการตรวจสอบประเภทและไม่ใช่กรณีดังกล่าว Once the run-time system figured out it would need to call a templatized virtual function- ไม่ว่าจะเป็นฟังก์ชั่นเสมือนหรือไม่เป็นที่รู้จักในเวลารวบรวม
dtech

9
@ddriver: 1.หากคอมไพเลอร์เห็นvoid f(concr_base& cb, virt_base& vb) { cb.f(); vb.f(); }ดังนั้น "รู้" ว่าฟังก์ชั่นใดถูกเรียกใช้ ณ จุดที่cb.f()เรียกว่าและไม่ทราบว่าเป็นvb.f()เช่นนั้น หลังจะต้องมีการพบที่รันไทม์ , โดยระบบรันไทม์ ไม่ว่าคุณจะต้องการเรียกสิ่งนี้ว่า "การหา" และสิ่งนี้มีประสิทธิภาพมากกว่าหรือน้อยกว่าก็ไม่เปลี่ยนข้อเท็จจริงเหล่านี้สักเล็กน้อย
sbi

9
@ddriver: 2.อินสแตนซ์ของฟังก์ชัน (สมาชิก) เทมเพลตคือฟังก์ชัน (สมาชิก) ดังนั้นจึงไม่มีปัญหาเลยด้วยการวางตัวชี้ไปยังอินสแตนซ์ดังกล่าวลงใน vtable แต่อินสแตนซ์เทมเพลตใดที่จำเป็นต้องทราบก็ต่อเมื่อมีการคอมไพล์ผู้โทรขณะที่ vtables ถูกตั้งค่าเมื่อคลาสพื้นฐานและคลาสที่ได้รับมาถูกคอมไพล์ และสิ่งเหล่านี้ล้วนถูกรวบรวมแยกจากกัน ยิ่งแย่ลง - คลาสที่ได้รับใหม่สามารถเชื่อมโยงไปยังระบบที่รันอยู่ขณะรันไทม์ (คิดว่าเบราว์เซอร์ของคุณกำลังโหลดปลั๊กอินแบบไดนามิก) แม้แต่ซอร์สโค้ดของผู้เรียกอาจหายไปนานเมื่อสร้างคลาสที่ได้รับใหม่
sbi

9
@sbi: ทำไมคุณตั้งสมมติฐานตามชื่อของฉัน ฉันไม่สับสนเกี่ยวกับข้อมูลทั่วไปและแม่แบบ ฉันรู้ว่าข้อมูลทั่วไปของ Java นั้นใช้เวลาหมดจด คุณไม่ได้อธิบายอย่างถี่ถ้วนว่าทำไมคุณไม่สามารถมีแม่แบบฟังก์ชันสมาชิกเสมือนใน C ++ แต่ InQsitive ทำ คุณทำให้เทมเพลตและกลไกเสมือนง่ายเกินไปจนเกินไปเพื่อ 'รวบรวมเวลา' และ 'รันไทม์' และสรุปว่า "คุณไม่สามารถมีแม่แบบฟังก์ชันสมาชิกเสมือนได้" ฉันอ้างอิงคำตอบของ InQsitive ซึ่งอ้างอิง "แม่แบบ C ++ The Complete Guide" ฉันไม่คิดว่าจะเป็น "โบกมือ" ขอให้มีความสุขมาก ๆ ในวันนี้นะ
Javanator

133

จากเทมเพลต C ++ คู่มือที่สมบูรณ์:

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


8
ฉันคิดว่าคอมไพเลอร์และลิงค์ C ++ ของวันนี้โดยเฉพาะอย่างยิ่งกับการสนับสนุนการเพิ่มประสิทธิภาพเวลาเชื่อมโยงควรจะสามารถสร้าง vtables และ offsets ที่จำเป็นในเวลาลิงค์ ดังนั้นบางทีเราจะได้รับคุณลักษณะนี้ใน C ++ 2b?
Kai Petzke

33

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

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


5
@pmr: ฟังก์ชั่นเสมือนอาจถูกเรียกจากรหัสที่ไม่ได้มีอยู่เมื่อรวบรวมฟังก์ชั่น คอมไพเลอร์จะกำหนดอินสแตนซ์ของฟังก์ชันสมาชิกเทมเพลตเสมือน (ตามทฤษฎี) ว่าจะสร้างรหัสที่ไม่มีอยู่ได้อย่างไร
sbi

2
@sbi: ใช่การรวบรวมแยกต่างหากจะเป็นปัญหาใหญ่ ฉันไม่มีความเชี่ยวชาญในการคอมไพล์เลอร์ C ++ เลยฉันไม่สามารถเสนอทางออกได้ เช่นเดียวกับฟังก์ชั่น templated โดยทั่วไปมันควรจะถูกสร้างอินสแตนซ์อีกครั้งในทุกหน่วยการคอมไพล์ใช่ไหม? จะไม่แก้ปัญหาเหรอ?
pmr

2
@sbi หากคุณอ้างถึงการโหลดไลบรารี่แบบไดนามิกนั่นเป็นปัญหาทั่วไปเกี่ยวกับเทมเพลต / ฟังก์ชั่นเทมเพลตไม่ใช่เพียงแค่วิธีการเทมเพลตเสมือน
โอ๊ก

"C ++ ไม่อนุญาตให้ [... ]" - จะขอบคุณที่ได้เห็นการอ้างอิงถึงมาตรฐาน (ไม่ว่าจะเป็นหนึ่งในปัจจุบันเมื่อเขียนคำตอบหรือหนึ่งถึงแปดปีต่อมา) ...
Aconcagua

19

ตารางฟังก์ชั่นเสมือนจริง

เริ่มจากพื้นหลังบนโต๊ะฟังก์ชั่นเสมือนจริงและวิธีการทำงาน (ที่มา ):

[20.3] ความแตกต่างระหว่างวิธีการเรียกฟังก์ชั่นสมาชิกเสมือนและไม่เสมือนคืออะไร?

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

ในทางตรงกันข้ามฟังก์ชั่นสมาชิกเสมือนได้รับการแก้ไขแบบไดนามิก (ในเวลาทำงาน) นั่นคือฟังก์ชั่นสมาชิกถูกเลือกแบบไดนามิก (ในเวลาทำงาน) ตามประเภทของวัตถุไม่ใช่ประเภทของตัวชี้ / การอ้างอิงไปยังวัตถุนั้น สิ่งนี้เรียกว่า "การรวมแบบไดนามิก" คอมไพเลอร์ส่วนใหญ่ใช้ตัวแปรบางส่วนของเทคนิคต่อไปนี้: หากวัตถุมีฟังก์ชันเสมือนหนึ่งฟังก์ชันขึ้นไปคอมไพเลอร์จะวางตัวชี้ที่ซ่อนอยู่ในวัตถุที่เรียกว่า "ตัวชี้เสมือนจริง" หรือ "ตัวชี้ v" v-pointer นี้ชี้ไปที่ตารางทั่วโลกที่เรียกว่า "virtual-table" หรือ "v-table"

คอมไพเลอร์สร้าง v-table สำหรับแต่ละคลาสที่มีฟังก์ชันเสมือนอย่างน้อยหนึ่งฟังก์ชัน ตัวอย่างเช่นหาก class Circle มีฟังก์ชันเสมือนสำหรับ draw () และ move () และ resize () จะมีหนึ่ง v-table ที่เกี่ยวข้องกับ class Circle แม้ว่าจะมีวัตถุ Circle gazillion และตัวชี้ v ของ แต่ละวัตถุ Circle เหล่านั้นจะชี้ไปที่ Circle v-table ตาราง v นั้นมีพอยน์เตอร์สำหรับแต่ละฟังก์ชันเสมือนในคลาส ตัวอย่างเช่น Circle v-table จะมีสามพอยน์เตอร์: ตัวชี้ไปที่ Circle :: draw (), ตัวชี้ไปที่ Circle :: move () และตัวชี้ไปที่ Circle :: resize ()

ในระหว่างการแจกจ่ายฟังก์ชันเสมือนระบบรันไทม์จะติดตามตัวชี้ v ของวัตถุไปยังตาราง v ของคลาสจากนั้นติดตามช่องที่เหมาะสมใน v-table ไปยังรหัสวิธีการ

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


ปัญหาของฉันหรือฉันมาที่นี่ได้อย่างไร

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

บางรหัส:

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

สิ่งที่ฉันต้องการให้เป็น แต่จะไม่รวบรวมเนื่องจากคอมโบเสมือน templated:

template<class T>
    virtual void  LoadCube(UtpBipCube<T> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

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

สารละลาย

คำเตือนนี่ไม่สวยมาก แต่มันอนุญาตให้ฉันลบรหัสการดำเนินการซ้ำ ๆ

1) ในคลาสฐาน

virtual void  LoadCube(UtpBipCube<float> &Cube,long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;
virtual void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
            long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1) = 0;

2) และในชั้นเรียนของเด็ก

void  LoadCube(UtpBipCube<float> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

void  LoadCube(UtpBipCube<unsigned short> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1)
{ LoadAnyCube(Cube,LowerLeftRow,LowerLeftColumn,UpperRightRow,UpperRightColumn,LowerBand,UpperBand); }

template<class T>
void  LoadAnyCube(UtpBipCube<T> &Cube, long LowerLeftRow=0,long LowerLeftColumn=0,
        long UpperRightRow=-1,long UpperRightColumn=-1,long LowerBand=0,long UpperBand=-1);

โปรดทราบว่า LoadAnyCube จะไม่ถูกประกาศในคลาสฐาน


นี่เป็นอีกหนึ่งคำตอบที่กองล้นกับการทำงานรอบ: ต้องมีวิธีแก้ปัญหาแม่แบบเสมือนสมาชิก


1
ฉันได้พบกับสถานการณ์เดียวกันและโครงสร้างการสืบทอดของชั้นเรียนจำนวนมาก มาโครช่วย
ZFY

16

รหัสต่อไปนี้สามารถรวบรวมและทำงานอย่างถูกต้องโดยใช้ MinGW G ++ 3.4.5 ในหน้าต่าง 7:

#include <iostream>
#include <string>

using namespace std;

template <typename T>
class A{
public:
    virtual void func1(const T& p)
    {
        cout<<"A:"<<p<<endl;
    }
};

template <typename T>
class B
: public A<T>
{
public:
    virtual void func1(const T& p)
    {
        cout<<"A<--B:"<<p<<endl;
    }
};

int main(int argc, char** argv)
{
    A<string> a;
    B<int> b;
    B<string> c;

    A<string>* p = &a;
    p->func1("A<string> a");
    p = dynamic_cast<A<string>*>(&c);
    p->func1("B<string> c");
    B<int>* q = &b;
    q->func1(3);
}

และผลลัพธ์คือ:

A:A<string> a
A<--B:B<string> c
A<--B:3

และต่อมาฉันได้เพิ่ม class X ใหม่:

class X
{
public:
    template <typename T>
    virtual void func2(const T& p)
    {
        cout<<"C:"<<p<<endl;
    }
};

เมื่อฉันพยายามใช้ class X ใน main () ดังนี้:

X x;
x.func2<string>("X x");

g ++ รายงานข้อผิดพลาดต่อไปนี้:

vtempl.cpp:34: error: invalid use of `virtual' in template declaration of `virtu
al void X::func2(const T&)'

ดังนั้นจึงเป็นที่ชัดเจนว่า:

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

19
แม่แบบคลาสอาจมีฟังก์ชันสมาชิกเสมือน ฟังก์ชันสมาชิกอาจไม่ใช่ทั้งแม่แบบฟังก์ชันสมาชิกและฟังก์ชันสมาชิกเสมือน
James McNellis

1
มันล้มเหลวจริงด้วย gcc 4.4.3 ในระบบของฉันแน่นอนว่า Ubuntu 10.04
blueskin

3
สิ่งนี้ต่างจากคำถามที่ถามมาทั้งหมด ที่นี่คลาสฐานทั้งหมดถูกสร้างเท็มเพลต ฉันได้รวบรวมสิ่งนี้มาก่อน สิ่งนี้จะคอมไพล์ใน Visual Studio 2010 ด้วย
ds-bos-msk

14

ไม่พวกเขาไม่สามารถ แต่:

template<typename T>
class Foo {
public:
  template<typename P>
  void f(const P& p) {
    ((T*)this)->f<P>(p);
  }
};

class Bar : public Foo<Bar> {
public:
  template<typename P>
  void f(const P& p) {
    std::cout << p << std::endl;
  }
};

int main() {
  Bar bar;

  Bar *pbar = &bar;
  pbar -> f(1);

  Foo<Bar> *pfoo = &bar;
  pfoo -> f(1);
};

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


3
นี้เรียกว่า CRTP ถ้าใครอยากรู้
Michael Choi

1
แต่สิ่งนี้ไม่ได้ช่วยในกรณีที่มีลำดับชั้นของคลาสและต้องการเรียกวิธีการเสมือนของพอยน์เตอร์ไปยังคลาสพื้นฐาน คุณFooชี้มีคุณสมบัติตามที่Foo<Bar>มันไม่สามารถชี้ไปที่หรือFoo<Barf> Foo<XXX>
Kai Petzke

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

8

ไม่ฟังก์ชันสมาชิกเทมเพลตไม่สามารถเป็นเสมือนได้


9
ความอยากรู้ของฉันคือ: ทำไม คอมไพเลอร์เผชิญปัญหาในการทำเช่นไร?
WannaBeGeek

1
คุณต้องมีการประกาศในขอบเขต (อย่างน้อยเพื่อให้ประเภทถูกต้อง) เป็นสิ่งจำเป็นโดยมาตรฐาน (และภาษา) เพื่อให้มีการประกาศในขอบเขตสำหรับตัวระบุที่คุณใช้
dirkgently

4

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

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

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

อย่างไรก็ตามมีความจำเป็นต้องกำหนดสำหรับแต่ละประเภทเทมเพลตรวมถึงฟังก์ชั่น wrapper เสมือนจริง:

#include <memory>
#include <iostream>
#include <iomanip>

//---------------------------------------------
// Abstract class with virtual functions
class Geometry {
public:
    virtual void getArea(float &area) = 0;
    virtual void getArea(long double &area) = 0;
};

//---------------------------------------------
// Square
class Square : public Geometry {
public:
    float size {1};

    // virtual wrapper functions call template function for square
    virtual void getArea(float &area) { getAreaT(area); }
    virtual void getArea(long double &area) { getAreaT(area); }

private:
    // Template function for squares
    template <typename T>
    void getAreaT(T &area) {
        area = static_cast<T>(size * size);
    }
};

//---------------------------------------------
// Circle
class Circle : public Geometry  {
public:
    float radius {1};

    // virtual wrapper functions call template function for circle
    virtual void getArea(float &area) { getAreaT(area); }
    virtual void getArea(long double &area) { getAreaT(area); }

private:
    // Template function for Circles
    template <typename T>
    void getAreaT(T &area) {
        area = static_cast<T>(radius * radius * 3.1415926535897932385L);
    }
};


//---------------------------------------------
// Main
int main()
{
    // get area of square using template based function T=float
    std::unique_ptr<Geometry> geometry = std::make_unique<Square>();
    float areaSquare;
    geometry->getArea(areaSquare);

    // get area of circle using template based function T=long double
    geometry = std::make_unique<Circle>();
    long double areaCircle;
    geometry->getArea(areaCircle);

    std::cout << std::setprecision(20) << "Square area is " << areaSquare << ", Circle area is " << areaCircle << std::endl;
    return 0;
}

เอาท์พุท:

พื้นที่สแควร์คือ 1, พื้นที่วงกลมคือ 3.1415926535897932385

ลองที่นี่


3

วิธีตอบคำถามส่วนที่สอง:

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

นี่ไม่ใช่สิ่งที่ไร้เหตุผลที่ต้องการทำ ตัวอย่างเช่น Java (ซึ่งทุกวิธีเป็นเสมือน) ไม่มีปัญหากับวิธีการทั่วไป

ตัวอย่างหนึ่งในภาษาซีพลัสพลัสที่ต้องการเทมเพลตฟังก์ชันเสมือนคือฟังก์ชันสมาชิกที่ยอมรับตัววนซ้ำทั่วไป หรือฟังก์ชันสมาชิกที่ยอมรับวัตถุฟังก์ชันทั่วไป

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


6
Java generics เป็นน้ำตาลประโยคสำหรับการคัดเลือกนักแสดง มันไม่เหมือนกับเทมเพลต
Brice M. Dempsey

2
@ BriceM.Dempsey: คุณสามารถพูดได้ว่าการคัดเลือกนักแสดงเป็นวิธีที่ Java ใช้ Generics แทนวิธีอื่น ๆ ... และแบบ sematically, exclipy ที่ใช้งานได้นั้นเป็น IMO ที่ถูกต้อง
einpoklum

2

มีวิธีแก้ไขสำหรับ 'วิธีแม่แบบเสมือนจริง' หากทราบประเภทชุดสำหรับวิธีแม่แบบล่วงหน้า

ในการแสดงแนวคิดในตัวอย่างด้านล่างมีการใช้งานเพียงสองประเภทเท่านั้น ( intและdouble )

มีวิธีแม่แบบ 'เสมือน' ( Base::Method) เรียกวิธีเสมือนที่สอดคล้องกัน (หนึ่งในBase::VMethod) ซึ่งในทางกลับกันเรียกการใช้วิธีการแม่แบบ (Impl::TMethod )

เดียวต้องใช้วิธีการแม่แบบTMethodในการใช้งานมา ( AImpl, BImpl) Derived<*Impl>และการใช้งาน

class Base
{
public:
    virtual ~Base()
    {
    }

    template <typename T>
    T Method(T t)
    {
        return VMethod(t);
    }

private:
    virtual int VMethod(int t) = 0;
    virtual double VMethod(double t) = 0;
};

template <class Impl>
class Derived : public Impl
{
public:
    template <class... TArgs>
    Derived(TArgs&&... args)
        : Impl(std::forward<TArgs>(args)...)
    {
    }

private:
    int VMethod(int t) final
    {
        return Impl::TMethod(t);
    }

    double VMethod(double t) final
    {
        return Impl::TMethod(t);
    }
};

class AImpl : public Base
{
protected:
    AImpl(int p)
        : i(p)
    {
    }

    template <typename T>
    T TMethod(T t)
    {
        return t - i;
    }

private:
    int i;
};

using A = Derived<AImpl>;

class BImpl : public Base
{
protected:
    BImpl(int p)
        : i(p)
    {
    }

    template <typename T>
    T TMethod(T t)
    {
        return t + i;
    }

private:
    int i;
};

using B = Derived<BImpl>;

int main(int argc, const char* argv[])
{
    A a(1);
    B b(1);
    Base* base = nullptr;

    base = &a;
    std::cout << base->Method(1) << std::endl;
    std::cout << base->Method(2.0) << std::endl;

    base = &b;
    std::cout << base->Method(1) << std::endl;
    std::cout << base->Method(2.0) << std::endl;
}

เอาท์พุท:

0
1
2
3

หมายเหตุ: Base::Methodเกินจริงสำหรับรหัสจริง ( VMethodสามารถทำให้เป็นสาธารณะและใช้โดยตรง) ฉันเพิ่มมันเพื่อให้ดูเหมือนเป็นวิธีแม่แบบ 'เสมือนจริง'


ฉันคิดวิธีแก้ปัญหานี้ขณะแก้ไขปัญหาในที่ทำงาน ดูเหมือนว่าคล้ายกับของ Mark Essel ด้านบน แต่ฉันหวังว่าจะมีการใช้งานและอธิบายได้ดีขึ้น
sad1raf

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

Essels วิธีการที่แตกต่างกันโดยสิ้นเชิง: หน้าที่เสมือนสามัญยอมรับ instantiations แม่แบบที่แตกต่าง - ฟังก์ชั่นและแม่แบบสุดท้ายในชั้นเรียนมาเพียงทำหน้าที่เพื่อหลีกเลี่ยงความซ้ำซ้อนรหัสและไม่ได้มีส่วนเคาน์เตอร์ในชั้นฐาน ...
Aconcagua

2

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

// abstract.h

// Simply define the types that each concrete class will use
#define IMPL_RENDER() \
    void render(int a, char *b) override { render_internal<char>(a, b); }   \
    void render(int a, short *b) override { render_internal<short>(a, b); } \
    // ...

class Renderable
{
public:
    // Then, once for each on the abstract
    virtual void render(int a, char *a) = 0;
    virtual void render(int a, short *b) = 0;
    // ...
};

ดังนั้นตอนนี้เพื่อใช้คลาสย่อยของเรา:

class Box : public Renderable
{
public:
    IMPL_RENDER() // Builds the functions we want

private:
    template<typename T>
    void render_internal(int a, T *b); // One spot for our logic
};

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


0

อย่างน้อยที่สุดด้วยฟังก์ชันเสมือน gcc 5.4 อาจเป็นสมาชิกเทมเพลต แต่ต้องเป็นเทมเพลตด้วยตนเอง

#include <iostream>
#include <string>
class first {
protected:
    virtual std::string  a1() { return "a1"; }
    virtual std::string  mixt() { return a1(); }
};

class last {
protected:
    virtual std::string a2() { return "a2"; }
};

template<class T>  class mix: first , T {
    public:
    virtual std::string mixt() override;
};

template<class T> std::string mix<T>::mixt() {
   return a1()+" before "+T::a2();
}

class mix2: public mix<last>  {
    virtual std::string a1() override { return "mix"; }
};

int main() {
    std::cout << mix2().mixt();
    return 0;
}

เอาท์พุท

mix before a2
Process finished with exit code 0

0

ลองสิ่งนี้:

เขียนใน classeder.h:

template <typename T>
class Example{
public:
    T c_value;

    Example(){}

    T Set(T variable)
    {
          return variable;
    }

    virtual Example VirtualFunc(Example paraM)
    {
         return paraM.Set(c_value);
    }

ตรวจสอบหากทำงานกับสิ่งนี้เพื่อเขียนโค้ดนี้ใน main.cpp:

#include <iostream>
#include <classeder.h>

int main()
{
     Example exmpl;
     exmpl.c_value = "Hello, world!";
     std::cout << exmpl.VirtualFunc(exmpl);
     return 0;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.