การสร้างอินสแตนซ์เทมเพลตที่ชัดเจน - ใช้เมื่อใด


95

หลังจากหยุดพักไปสองสามสัปดาห์ฉันกำลังพยายามขยายและขยายความรู้เกี่ยวกับเทมเพลตด้วยเทมเพลตหนังสือ- The Complete Guideโดย David Vandevoorde และ Nicolai M. Josuttis และสิ่งที่ฉันพยายามทำความเข้าใจในตอนนี้คือการสร้างเทมเพลตอย่างชัดเจน .

ฉันไม่ได้มีปัญหากับกลไกเช่นนี้ แต่ฉันนึกไม่ออกว่าฉันต้องการหรือต้องการใช้คุณสมบัตินี้ ถ้าใครสามารถอธิบายให้ฉันฟังได้ฉันจะขอบคุณมากกว่านี้

คำตอบ:


67

คัดลอกโดยตรงจากhttps://docs.microsoft.com/en-us/cpp/cpp/explicit-instantiation :

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

(ตัวอย่างเช่น libstdc ++ มีการสร้างอินสแตนซ์ที่ชัดเจนของstd::basic_string<char,char_traits<char>,allocator<char> >(ซึ่งเป็นstd::string) ดังนั้นทุกครั้งที่คุณใช้ฟังก์ชันของstd::stringโค้ดฟังก์ชันเดียวกันไม่จำเป็นต้องคัดลอกไปยังอ็อบเจ็กต์คอมไพเลอร์จำเป็นต้องอ้างถึง (ลิงก์) ที่เป็น libstdc ++ เท่านั้น


8
ใช่ไลบรารี MSVC CRT มีอินสแตนซ์ที่ชัดเจนสำหรับสตรีมโลแคลและคลาสสตริงทั้งหมดโดยเฉพาะสำหรับ char และ wchar_t .lib ที่ได้มีขนาดเกิน 5 เมกะไบต์
Hans Passant

4
คอมไพลเลอร์รู้ได้อย่างไรว่าเทมเพลตนั้นถูกสร้างอินสแตนซ์อย่างชัดเจนที่อื่น จะไม่เพียงสร้างนิยามคลาสเพราะพร้อมใช้งานหรือไม่?

@STing: หากเทมเพลตถูกสร้างอินสแตนซ์จะมีรายการฟังก์ชันเหล่านั้นในตารางสัญลักษณ์
kennytm

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

4
@ เคนนี่: ฉันรู้เกี่ยวกับตัวเลือก GCC เพื่อป้องกันการสร้างอินสแตนซ์โดยปริยาย แต่นี่ไม่ใช่มาตรฐาน เท่าที่ฉันรู้ VC ++ ไม่มีตัวเลือกดังกล่าว Explicit inst. มักถูกขนานนามว่าปรับปรุงเวลาคอมไพล์ / ลิงค์ (แม้โดย Bjarne) แต่เพื่อให้สามารถตอบสนองวัตถุประสงค์นั้นคอมไพเลอร์ต้องรู้อย่างใดที่จะไม่สร้างอินสแตนซ์เทมเพลตโดยปริยาย (เช่นผ่านแฟล็ก GCC) หรือต้องไม่ให้ คำจำกัดความของเทมเพลตเป็นเพียงการประกาศเท่านั้น เสียงนี้ถูกต้องหรือไม่? ฉันแค่พยายามทำความเข้าใจว่าเหตุใดจึงต้องใช้การสร้างอินสแตนซ์ที่ชัดเจน (นอกเหนือจากการ จำกัด ประเภทคอนกรีต)

85

หากคุณกำหนดคลาสเทมเพลตที่คุณต้องการใช้กับประเภท Explicit สองสามประเภทเท่านั้น

ใส่การประกาศเทมเพลตในไฟล์ส่วนหัวเหมือนกับคลาสปกติ

ใส่นิยามเทมเพลตในซอร์สไฟล์เหมือนกับคลาสปกติ

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

ตัวอย่างโง่ ๆ :

// StringAdapter.h
template<typename T>
class StringAdapter
{
     public:
         StringAdapter(T* data);
         void doAdapterStuff();
     private:
         std::basic_string<T> m_data;
};
typedef StringAdapter<char>    StrAdapter;
typedef StringAdapter<wchar_t> WStrAdapter;

ที่มา:

// StringAdapter.cpp
#include "StringAdapter.h"

template<typename T>
StringAdapter<T>::StringAdapter(T* data)
    :m_data(data)
{}

template<typename T>
void StringAdapter<T>::doAdapterStuff()
{
    /* Manipulate a string */
}

// Explicitly instantiate only the classes you want to be defined.
// In this case I only want the template to work with characters but
// I want to support both char and wchar_t with the same code.
template class StringAdapter<char>;
template class StringAdapter<wchar_t>;

หลัก

#include "StringAdapter.h"

// Note: Main can not see the definition of the template from here (just the declaration)
//       So it relies on the explicit instantiation to make sure it links.
int main()
{
  StrAdapter  x("hi There");
  x.doAdapterStuff();
}

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

1
@ user123456: อาจขึ้นอยู่กับคอมไพเลอร์ แต่น่าจะเป็นจริงในสถานการณ์ส่วนใหญ่
Martin York

1
มีวิธีใดบ้างที่จะทำให้คอมไพลเลอร์ใช้เวอร์ชันที่สร้างอินสแตนซ์อย่างชัดเจนนี้สำหรับประเภทที่คุณระบุไว้ล่วงหน้า แต่ถ้าคุณพยายามสร้างอินสแตนซ์เทมเพลตด้วยประเภท "แปลก / ไม่คาดคิด" ให้ใช้งาน "ตามปกติ" โดยที่มันเป็นเพียง สร้างเทมเพลตตามต้องการหรือไม่
David Doria

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

7
การพูดคุยระหว่างความคิดเห็นข้างต้นส่วนใหญ่ไม่เป็นความจริงอีกต่อไปเนื่องจาก c ++ 11: การประกาศการสร้างอินสแตนซ์ที่ชัดเจน (เทมเพลตภายนอก) ป้องกันการสร้างอินสแตนซ์โดยปริยาย: โค้ดที่จะทำให้เกิดการสร้างอินสแตนซ์โดยนัยจะต้องใช้นิยามอินสแตนซ์ที่ชัดเจนซึ่งมีให้ที่อื่นใน โปรแกรม (โดยทั่วไปในไฟล์อื่น: สามารถใช้เพื่อลดเวลาการคอมไพล์) en.cppreference.com/w/cpp/language/class_template
xaxxon

21

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

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

  • ลบคำจำกัดความออกจากส่วนหัวเพื่อป้องกันเครื่องมือสร้างจากการสร้างใหม่รวมถึง
  • การกำหนดนิยามใหม่ของวัตถุ

ลบคำจำกัดความออกจากส่วนหัว

การสร้างอินสแตนซ์ที่ชัดเจนอนุญาตให้คุณทิ้งนิยามไว้ในไฟล์. cpp

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

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

ดูตัวอย่างที่เป็นรูปธรรมด้านล่าง

การกำหนดนิยามใหม่ได้รับ: การทำความเข้าใจปัญหา

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

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

นี่คือตัวอย่างที่เป็นรูปธรรมซึ่งกำหนดทั้งโดยนัยmain.cppและnotmain.cppโดยปริยายMyTemplate<int>เนื่องจากการใช้งานในไฟล์เหล่านั้น

main.cpp

#include <iostream>

#include "mytemplate.hpp"
#include "notmain.hpp"

int main() {
    std::cout << notmain() + MyTemplate<int>().f(1) << std::endl;
}

notmain.cpp

#include "mytemplate.hpp"
#include "notmain.hpp"

int notmain() { return MyTemplate<int>().f(1); }

mytemplate.hpp

#ifndef MYTEMPLATE_HPP
#define MYTEMPLATE_HPP

template<class T>
struct MyTemplate {
    T f(T t) { return t + 1; }
};

#endif

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

int notmain();

#endif

GitHub อัปสตรี

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

g++ -c -Wall -Wextra -std=c++11 -pedantic-errors -o notmain.o notmain.cpp
g++ -c -Wall -Wextra -std=c++11 -pedantic-errors -o main.o main.cpp
g++    -Wall -Wextra -std=c++11 -pedantic-errors -o main.out notmain.o main.o
echo notmain.o
nm -C -S notmain.o | grep MyTemplate
echo main.o
nm -C -S main.o | grep MyTemplate

เอาท์พุต:

notmain.o
0000000000000000 0000000000000017 W MyTemplate<int>::f(int)
main.o
0000000000000000 0000000000000017 W MyTemplate<int>::f(int)

จากman nmนั้นเราจะเห็นว่าWหมายถึงสัญลักษณ์ที่อ่อนแอซึ่ง GCC เลือกเพราะนี่คือฟังก์ชันเทมเพลต สัญลักษณ์ที่อ่อนแอหมายความว่าโค้ดที่คอมไพล์สร้างขึ้นโดยปริยายMyTemplate<int>ถูกคอมไพล์บนไฟล์ทั้งสอง

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

ตัวเลขในผลลัพธ์หมายถึง:

  • 0000000000000000: ที่อยู่ภายในส่วน ศูนย์นี้เป็นเพราะเทมเพลตถูกใส่ลงในส่วนของตัวเองโดยอัตโนมัติ
  • 0000000000000017: ขนาดของรหัสที่สร้างขึ้นสำหรับพวกเขา

เราจะเห็นสิ่งนี้ชัดเจนขึ้นเล็กน้อยด้วย:

objdump -S main.o | c++filt

ซึ่งลงท้ายด้วย:

Disassembly of section .text._ZN10MyTemplateIiE1fEi:

0000000000000000 <MyTemplate<int>::f(int)>:
   0:   f3 0f 1e fa             endbr64 
   4:   55                      push   %rbp
   5:   48 89 e5                mov    %rsp,%rbp
   8:   48 89 7d f8             mov    %rdi,-0x8(%rbp)
   c:   89 75 f4                mov    %esi,-0xc(%rbp)
   f:   8b 45 f4                mov    -0xc(%rbp),%eax
  12:   83 c0 01                add    $0x1,%eax
  15:   5d                      pop    %rbp
  16:   c3                      retq

และ_ZN10MyTemplateIiE1fEiเป็นชื่อที่สับสนMyTemplate<int>::f(int)>ซึ่งc++filtตัดสินใจที่จะไม่เข้าใจผิด

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

แนวทางแก้ไขปัญหาการกำหนดนิยามวัตถุใหม่

ปัญหานี้สามารถหลีกเลี่ยงได้โดยใช้การสร้างอินสแตนซ์ที่ชัดเจนและอย่างใดอย่างหนึ่ง:

  • ให้คำจำกัดความบน hpp และเพิ่มextern templateใน hpp สำหรับประเภทที่กำลังจะถูกสร้างอินสแตนซ์อย่างชัดเจน

    ตามที่อธิบายไว้ที่: การใช้เทมเพลตภายนอก (C ++ 11) extern templateป้องกันไม่ให้เทมเพลตที่กำหนดอย่างสมบูรณ์ถูกสร้างอินสแตนซ์โดยหน่วยคอมไพล์ยกเว้นการสร้างอินสแตนซ์ที่ชัดเจนของเรา ด้วยวิธีนี้เฉพาะการสร้างอินสแตนซ์ที่ชัดเจนของเราเท่านั้นที่จะถูกกำหนดในออบเจ็กต์สุดท้าย:

    mytemplate.hpp

    #ifndef MYTEMPLATE_HPP
    #define MYTEMPLATE_HPP
    
    template<class T>
    struct MyTemplate {
        T f(T t) { return t + 1; }
    };
    
    extern template class MyTemplate<int>;
    
    #endif
    

    mytemplate.cpp

    #include "mytemplate.hpp"
    
    // Explicit instantiation required just for int.
    template class MyTemplate<int>;
    

    main.cpp

    #include <iostream>
    
    #include "mytemplate.hpp"
    #include "notmain.hpp"
    
    int main() {
        std::cout << notmain() + MyTemplate<int>().f(1) << std::endl;
    }
    

    notmain.cpp

    #include "mytemplate.hpp"
    #include "notmain.hpp"
    
    int notmain() { return MyTemplate<int>().f(1); }
    

    ข้อเสีย:

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

    mytemplate.hpp

    #ifndef MYTEMPLATE_HPP
    #define MYTEMPLATE_HPP
    
    template<class T>
    struct MyTemplate {
        T f(T t);
    };
    
    #endif
    

    mytemplate.cpp

    #include "mytemplate.hpp"
    
    template<class T>
    T MyTemplate<T>::f(T t) { return t + 1; }
    
    // Explicit instantiation.
    template class MyTemplate<int>;
    

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

  • เก็บคำจำกัดความไว้ใน hpp และเพิ่มextern templateในส่วนรวมทั้งหมด:

    mytemplate.cpp

    #include "mytemplate.hpp"
    
    // Explicit instantiation.
    template class MyTemplate<int>;
    

    main.cpp

    #include <iostream>
    
    #include "mytemplate.hpp"
    #include "notmain.hpp"
    
    // extern template declaration
    extern template class MyTemplate<int>;
    
    int main() {
        std::cout << notmain() + MyTemplate<int>().f(1) << std::endl;
    }
    

    notmain.cpp

    #include "mytemplate.hpp"
    #include "notmain.hpp"
    
    // extern template declaration
    extern template class MyTemplate<int>;
    
    int notmain() { return MyTemplate<int>().f(1); }
    

    ข้อเสีย: ผู้รวมทั้งหมดต้องเพิ่มexternไฟล์ CPP ซึ่งโปรแกรมเมอร์อาจลืมทำ

ด้วยโซลูชันเหล่าnmนี้ตอนนี้ประกอบด้วย:

notmain.o
                 U MyTemplate<int>::f(int)
main.o
                 U MyTemplate<int>::f(int)
mytemplate.o
0000000000000000 W MyTemplate<int>::f(int)

ดังนั้นเราจึงเห็นว่ามีเพียงmytemplate.oการรวบรวมMyTemplate<int>ตามที่ต้องการในขณะที่notmain.oและmain.oไม่ได้เพราะUหมายถึงไม่ได้กำหนด

ลบคำจำกัดความออกจากส่วนหัวที่รวมไว้ แต่ยังแสดงเทมเพลต API ภายนอกในไลบรารีส่วนหัวเท่านั้น

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

อย่างไรก็ตามสำหรับไลบรารีส่วนหัวเท่านั้นหากคุณต้องการทั้งสอง:

  • เร่งการรวบรวมโครงการของคุณ
  • แสดงส่วนหัวเป็น API ไลบรารีภายนอกเพื่อให้ผู้อื่นใช้งานได้

จากนั้นคุณสามารถลองทำอย่างใดอย่างหนึ่งต่อไปนี้:

    • mytemplate.hpp: นิยามแม่แบบ
    • mytemplate_interface.hpp: การประกาศเทมเพลตที่ตรงกับคำจำกัดความจากmytemplate_interface.hppเท่านั้นไม่มีคำจำกัดความ
    • mytemplate.cpp: รวมmytemplate.hppและสร้างอินสแตนซ์ที่ชัดเจน
    • main.cppและที่อื่น ๆ ในฐานรหัส: รวมmytemplate_interface.hppไม่ใช่mytemplate.hpp
    • mytemplate.hpp: นิยามแม่แบบ
    • mytemplate_implementation.hpp: รวมmytemplate.hppและเพิ่มexternให้กับทุกชั้นเรียนที่จะสร้างอินสแตนซ์
    • mytemplate.cpp: รวมmytemplate.hppและสร้างอินสแตนซ์ที่ชัดเจน
    • main.cppและที่อื่น ๆ ในฐานรหัส: รวมmytemplate_implementation.hppไม่ใช่mytemplate.hpp

หรืออาจดีกว่าสำหรับหลายส่วนหัว: สร้างintf/ implโฟลเดอร์ในincludes/โฟลเดอร์ของคุณและใช้mytemplate.hppเป็นชื่อเสมอ

mytemplate_interface.hppวิธีการลักษณะเช่นนี้:

mytemplate.hpp

#ifndef MYTEMPLATE_HPP
#define MYTEMPLATE_HPP

#include "mytemplate_interface.hpp"

template<class T>
T MyTemplate<T>::f(T t) { return t + 1; }

#endif

mytemplate_interface.hpp

#ifndef MYTEMPLATE_INTERFACE_HPP
#define MYTEMPLATE_INTERFACE_HPP

template<class T>
struct MyTemplate {
    T f(T t);
};

#endif

mytemplate.cpp

#include "mytemplate.hpp"

// Explicit instantiation.
template class MyTemplate<int>;

main.cpp

#include <iostream>

#include "mytemplate_interface.hpp"

int main() {
    std::cout << MyTemplate<int>().f(1) << std::endl;
}

รวบรวมและเรียกใช้:

g++ -c -Wall -Wextra -std=c++11 -pedantic-errors -o mytemplate.o mytemplate.cpp
g++ -c -Wall -Wextra -std=c++11 -pedantic-errors -o main.o main.cpp
g++    -Wall -Wextra -std=c++11 -pedantic-errors -o main.out main.o mytemplate.o

เอาท์พุต:

2

ทดสอบใน Ubuntu 18.04

C ++ 20 โมดูล

https://en.cppreference.com/w/cpp/language/modules

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

คุณยังคงต้องทำการสร้างอินสแตนซ์อย่างชัดเจนเพื่อให้ได้ speedup / disk saving แต่อย่างน้อยเราก็มีวิธีแก้ปัญหาสำหรับ "ลบคำจำกัดความจากส่วนหัวที่รวมอยู่ แต่ยังเปิดเผยเทมเพลตเป็น API ภายนอก" ซึ่งไม่จำเป็นต้องคัดลอกสิ่งต่างๆประมาณ 100 ครั้ง

การใช้งานที่คาดหวัง (โดยไม่มีการสอดแทรกอย่างชัดเจนไม่แน่ใจว่าไวยากรณ์จะเป็นอย่างไรโปรดดูที่: วิธีใช้การสร้างอินสแตนซ์แบบชัดแจ้งของเทมเพลตกับโมดูล C ++ 20 ) เป็นสิ่งที่ควบคู่ไปกับ:

helloworld.cpp

export module helloworld;  // module declaration
import <iostream>;         // import declaration
 
template<class T>
export void hello(T t) {      // export declaration
    std::cout << t << std::end;
}

main.cpp

import helloworld;  // import declaration
 
int main() {
    hello(1);
    hello("world");
}

จากนั้นรวบรวมกล่าวถึงที่https://quuxplusone.github.io/blog/2019/11/07/modular-hello-world/

clang++ -std=c++2a -c helloworld.cpp -Xclang -emit-module-interface -o helloworld.pcm
clang++ -std=c++2a -c -o helloworld.o helloworld.cpp
clang++ -std=c++2a -fprebuilt-module-path=. -o main.out main.cpp helloworld.o

จากสิ่งนี้เราจะเห็นว่า clang สามารถแยกส่วนต่อประสานเทมเพลต + การนำไปใช้งานในเวทมนตร์helloworld.pcmซึ่งจะต้องมีการแทนค่ากลางของแหล่งที่มาของ LLVM: เทมเพลตถูกจัดการอย่างไรในระบบโมดูล C ++ ซึ่งยังคงทำให้ข้อกำหนดของเทมเพลตเกิดขึ้นได้

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

คุณมีโครงการที่ซับซ้อนและต้องการตัดสินใจว่าการสร้างอินสแตนซ์เทมเพลตจะนำมาซึ่งผลกำไรอย่างมีนัยสำคัญโดยไม่ต้องทำ Refactor จริงหรือไม่?

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

# List all weak symbols with size only, no address.
find . -name '*.o' | xargs -I{} nm -C --size-sort --radix d '{}' |
  grep ' W ' > nm.log

# Sort by symbol size.
sort -k1 -n nm.log -o nm.sort.log

# Get a repetition count.
uniq -c nm.sort.log > nm.uniq.log

# Find the most repeated/largest objects.
sort -k1,2 -n nm.uniq.log -o nm.uniq.sort.log

# Find the objects that would give you the most gain after refactor.
# This gain is calculated as "(n_occurences - 1) * size" which is
# the size you would gain for keeping just a single instance.
# If you are going to refactor anything, you should start with the ones
# at the bottom of this list. 
awk '{gain = ($1 - 1) * $2; print gain, $0}' nm.uniq.sort.log |
  sort -k1 -n > nm.gains.log

# Total gain if you refactored everything.
awk 'START{sum=0}{sum += $1}END{print sum}' nm.gains.log

# Total size. The closer total gain above is to total size, the more
# you would gain from the refactor.
awk 'START{sum=0}{sum += $1}END{print sum}' nm.log

ความฝัน: แคชของคอมไพเลอร์เทมเพลต

ฉันคิดว่าทางออกที่ดีที่สุดคือถ้าเราสามารถสร้างด้วย:

g++ --template-cache myfile.o file1.cpp
g++ --template-cache myfile.o file2.cpp

จากนั้นmyfile.oจะใช้เทมเพลตที่คอมไพล์ก่อนหน้านี้ซ้ำกับไฟล์ต่างๆโดยอัตโนมัติ

นี่หมายถึงความพยายามพิเศษ 0 สำหรับโปรแกรมเมอร์นอกเหนือจากการส่งตัวเลือก CLI พิเศษนั้นไปยังระบบบิลด์ของคุณ

โบนัสรองของการสร้างอินสแตนซ์เทมเพลตที่ชัดเจน: ช่วย IDEs การสร้างอินสแตนซ์เทมเพลตรายการ

ฉันพบว่า IDE บางตัวเช่น Eclipse ไม่สามารถแก้ไข "รายการของอินสแตนซ์เทมเพลตทั้งหมดที่ใช้"

ตัวอย่างเช่นหากคุณอยู่ในโค้ดเทมเพลตและคุณต้องการค้นหาค่าที่เป็นไปได้ของเทมเพลตคุณจะต้องค้นหาตัวสร้างใช้ทีละรายการและสรุปประเภทที่เป็นไปได้ทีละประเภท

แต่ใน Eclipse 2020-03 ฉันสามารถแสดงรายการเทมเพลตที่สร้างอินสแตนซ์อย่างชัดเจนได้อย่างง่ายดายโดยทำการค้นหา Find all usages (Ctrl + Alt + G) ในชื่อคลาสซึ่งชี้ให้ฉันเห็นเช่นจาก:

template <class T>
struct AnimalTemplate {
    T animal;
    AnimalTemplate(T animal) : animal(animal) {}
    std::string noise() {
        return animal.noise();
    }
};

ถึง:

template class AnimalTemplate<Dog>;

นี่คือตัวอย่าง: https://github.com/cirosantilli/ide-test-projects/blob/e1c7c6634f2d5cdeafd2bdc79bcfbb2057cb04c4/cpp/animal_template.hpp#L15

เทคนิคการรบแบบกองโจรอีกอย่างที่คุณสามารถใช้นอก IDE ได้อย่างไรก็ตามจะเรียกใช้nm -Cบนไฟล์ปฏิบัติการสุดท้ายและ grep ชื่อเทมเพลต

nm -C main.out | grep AnimalTemplate

ซึ่งชี้ให้เห็นโดยตรงถึงข้อเท็จจริงที่Dogเป็นหนึ่งในอินสแตนซ์:

0000000000004dac W AnimalTemplate<Dog>::noise[abi:cxx11]()
0000000000004d82 W AnimalTemplate<Dog>::AnimalTemplate(Dog)
0000000000004d82 W AnimalTemplate<Dog>::AnimalTemplate(Dog)

1

ขึ้นอยู่กับรุ่นของคอมไพเลอร์ - เห็นได้ชัดว่ามีโมเดล Borland และรุ่น CFront จากนั้นก็ขึ้นอยู่กับความตั้งใจของคุณด้วยเช่นกันหากคุณกำลังเขียนห้องสมุดคุณอาจ (ตามที่กล่าวอ้างข้างต้น) สร้างตัวอย่างความเชี่ยวชาญที่คุณต้องการอย่างชัดเจน

ลิขสิทธิ์ GNU C ++ หน้ากล่าวถึงรุ่นที่นี่https://gcc.gnu.org/onlinedocs/gcc-4.5.2/gcc/Template-Instantiation.html

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