เหตุใดฟังก์ชันอินไลน์ของ C ++ จึงอยู่ในส่วนหัว


120

หมายเหตุนี่ไม่ใช่คำถามเกี่ยวกับวิธีการใช้ฟังก์ชันแบบอินไลน์หรือวิธีการทำงานอีกต่อไปว่าทำไมพวกเขาถึงทำในแบบที่เป็นอยู่

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

struct foo{
    void bar(); // no need to define this as inline
}

เหตุใดการใช้ฟังก์ชันคลาสแบบอินไลน์จึงต้องอยู่ในไฟล์ส่วนหัว? เหตุใดฉันจึงไม่สามารถใส่ฟังก์ชันอินไลน์ใน.cppไฟล์ได้ ถ้าฉันพยายามใส่คำจำกัดความแบบอินไลน์ใน.cppไฟล์ฉันจะได้รับข้อผิดพลาดตามบรรทัดของ:

error LNK2019: unresolved external symbol 
"public: void __thiscall foo::bar(void)"
(?bar@foo@@QAEXXZ) referenced in function _main 
1>C:\Users\Me\Documents\Visual Studio 2012\Projects\inline\Debug\inline.exe 
: fatal error LNK1120: 1 unresolved externals



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

2
ในกรณีนี้ฉันคิดว่าคุณอาจเข้าใจผิดทั้ง "inline" หรือ "header files"; คำยืนยันของคุณไม่เป็นความจริง คุณสามารถใช้ฟังก์ชันสมาชิกแบบอินไลน์ได้และคุณสามารถใส่นิยามฟังก์ชันแบบอินไลน์ในไฟล์ส่วนหัวได้ซึ่งอาจไม่ใช่ความคิดที่ดี คุณสามารถชี้แจงคำถามของคุณได้หรือไม่?
CB Bailey

โพสต์แก้ไขผมคิดว่าคุณอาจจะถามเกี่ยวกับสถานการณ์เมื่อinlineปรากฏในความหมาย แต่ไม่ประกาศก่อนเทียบกับในทางกลับกัน ถ้าเป็นเช่นนั้นสิ่งนี้อาจช่วยได้: stackoverflow.com/questions/4924912/…
CB Bailey

คำตอบ:


122

คำจำกัดความของinlineฟังก์ชันไม่จำเป็นต้องอยู่ในไฟล์ส่วนหัว แต่เนื่องจากกฎคำนิยามเดียว ( ODR )สำหรับฟังก์ชันอินไลน์จึงต้องมีคำจำกัดความที่เหมือนกันสำหรับฟังก์ชันในทุกหน่วยการแปลที่ใช้

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

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

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


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

1
นี่เป็นคำตอบที่ดีกว่าของ+1ฉันจริงๆจากฉัน!
sbi

2
@thecoshman: มีสองความแตกต่าง ไฟล์ต้นฉบับกับไฟล์ส่วนหัว ตามหลักการแล้วไฟล์ส่วนหัวมักจะอ้างถึงไฟล์ต้นฉบับที่ไม่ได้เป็นพื้นฐานสำหรับหน่วยการแปล แต่เป็นเพียง # รวมจากไฟล์ต้นฉบับอื่น ๆ จากนั้นมีการประกาศเทียบกับคำจำกัดความ คุณสามารถมีการประกาศหรือคำจำกัดความของฟังก์ชันในไฟล์ส่วนหัวหรือไฟล์ต้นฉบับ "ปกติ" ฉันเกรงว่าฉันไม่แน่ใจว่าคุณกำลังถามอะไรในความคิดเห็นของคุณ
CB Bailey

ไม่ต้องกังวลฉันเข้าใจแล้วว่าทำไมถึงเป็นตอนนี้ ... แม้ว่าฉันไม่แน่ใจว่าใครตอบคำถามนี้จริงๆ การรวมกันของคุณและคำตอบของ @ Xanatos อธิบายให้ฉันฟัง
thecoshman

113

มีสองวิธีในการดู:

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

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

คำอธิบายสองข้อสรุปได้ว่าinlineคำหลักนั้นไม่ตรงตามที่คุณคาดหวัง

คอมไพเลอร์ C ++ มีอิสระที่จะใช้การเพิ่มประสิทธิภาพแบบอินไลน์(แทนที่การเรียกฟังก์ชันด้วยเนื้อความของฟังก์ชันที่เรียกโดยบันทึกค่าใช้จ่ายในการโทร) เมื่อใดก็ได้ที่ต้องการตราบใดที่ไม่ได้เปลี่ยนแปลงพฤติกรรมที่สังเกตได้ของโปรแกรม

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


23

นี่คือขีด จำกัด ของคอมไพเลอร์ C ++ หากคุณใส่ฟังก์ชันไว้ในส่วนหัวไฟล์ cpp ทั้งหมดที่สามารถอินไลน์ได้จะเห็น "แหล่งที่มา" ของฟังก์ชันของคุณและคอมไพเลอร์สามารถทำได้ อื่น ๆ การซับในจะต้องทำโดยตัวเชื่อม (แต่ละไฟล์ cpp จะถูกคอมไพล์ในไฟล์ obj แยกกัน) ปัญหาคือมันจะยากกว่ามากที่จะทำในตัวเชื่อมโยง มีปัญหาคล้ายกันกับคลาส / ฟังก์ชัน "เทมเพลต" พวกเขาจำเป็นต้องสร้างอินสแตนซ์โดยคอมไพเลอร์เนื่องจากตัวเชื่อมโยงจะมีปัญหาในการสร้างอินสแตนซ์ (การสร้างเวอร์ชันพิเศษ) คอมไพเลอร์ / ตัวเชื่อมที่ใหม่กว่าบางตัวสามารถทำการคอมไพเลอร์ / ลิงก์แบบ "two pass" โดยที่คอมไพเลอร์ทำพาสแรกจากนั้นตัวเชื่อมโยงจะทำงานและเรียกคอมไพเลอร์เพื่อแก้ไขสิ่งที่ไม่ได้รับการแก้ไข (อินไลน์ / แม่แบบ ... )


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

11
ฉันไม่เห็นด้วยกับคำตอบนี้ไม่ใช่ขีด จำกัด ของคอมไพเลอร์ C ++ มันเป็นวิธีการระบุกฎภาษาเท่านั้น กฎของภาษาอนุญาตให้ใช้รูปแบบการคอมไพล์อย่างง่าย แต่ไม่ได้ห้ามการใช้งานทางเลือกอื่น
CB Bailey

3
ฉันเห็นด้วยกับ @Charles ในความเป็นจริงมีคอมไพเลอร์ที่ทำหน้าที่แบบอินไลน์ในหน่วยการแปลดังนั้นสิ่งนี้ไม่ได้เกิดจากข้อ จำกัด ของคอมไพเลอร์
sbi

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

10

เหตุผลก็คือคอมไพลเลอร์ต้องดูคำจำกัดความจริงๆจึงจะสามารถวางลงในตำแหน่งของการเรียกได้

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


9

inlineคีย์เวิร์ดc ++ ทำให้เข้าใจผิดไม่ได้หมายความว่า "อินไลน์ฟังก์ชันนี้" หากฟังก์ชันถูกกำหนดเป็นแบบอินไลน์หมายความว่าสามารถกำหนดได้หลายครั้งตราบเท่าที่คำจำกัดความทั้งหมดเท่ากัน เป็นสิ่งที่ถูกกฎหมายอย่างสมบูรณ์สำหรับฟังก์ชันที่ทำเครื่องหมายinlineว่าเป็นฟังก์ชันจริงที่ถูกเรียกแทนการรับโค้ดแบบอินไลน์ ณ จุดที่เรียก

การกำหนดฟังก์ชันในไฟล์ส่วนหัวเป็นสิ่งจำเป็นสำหรับเทมเพลตเนื่องจากเช่นคลาสเทมเพลตไม่ใช่คลาสจริงๆจึงเป็นเทมเพลตสำหรับคลาสที่คุณสามารถสร้างได้หลายรูปแบบ เพื่อให้คอมไพลเลอร์สามารถสร้างFoo<int>::bar()ฟังก์ชันได้เมื่อคุณใช้เทมเพลต Foo เพื่อสร้างคลาส FooFoo<T>::bar()ต้องมองเห็นนิยามที่แท้จริงของ


และเนื่องจากเป็นแม่แบบสำหรับการเรียนก็ไม่ได้เรียกว่าแม่แบบเรียนแต่แม่แบบชั้นเรียน
sbi

4
ย่อหน้าแรกถูกต้องสมบูรณ์ (และฉันหวังว่าฉันจะเน้นคำว่า "ทำให้เข้าใจผิด") แต่ฉันไม่เห็นความจำเป็นสำหรับการไม่ต่อเนื่องเป็นเทมเพลต
Thomas Edleson

คอมไพเลอร์บางตัวจะใช้เป็นคำใบ้ว่าฟังก์ชันอาจจะอินไลน์ได้ แต่ไม่รับประกันว่าจะอินไลน์เพียงเพราะคุณประกาศinline(หรือไม่ประกาศinlineรับประกันว่าจะไม่อินไลน์)
Keith M

4

ฉันรู้ว่านี่เป็นกระทู้เก่า แต่คิดว่าฉันควรพูดถึงexternคีย์เวิร์ดนั้น เมื่อเร็ว ๆ นี้ฉันพบปัญหานี้และแก้ไขได้ดังนี้

Helper.h

namespace DX
{
    extern inline void ThrowIfFailed(HRESULT hr);
}

Helper.cpp

namespace DX
{
    inline void ThrowIfFailed(HRESULT hr)
    {
        if (FAILED(hr))
        {
            std::stringstream ss;
            ss << "#" << hr;
            throw std::exception(ss.str().c_str());
        }
    }
}

6
โดยทั่วไปสิ่งนี้จะไม่ส่งผลให้ฟังก์ชันถูกอินไลน์จริง ๆ เว้นแต่คุณจะใช้ Whole Program Optimization (WPO)
Chuck Walbourn

3

เพราะคอมไพเลอร์ต้องการที่จะเห็นพวกเขาในการสั่งซื้อเพื่อinlineพวกเขา และไฟล์ส่วนหัวเป็น "ส่วนประกอบ" ซึ่งมักรวมอยู่ในหน่วยการแปลอื่น ๆ

#include "file.h"
// Ok, now me (the compiler) can see the definition of that inline function. 
// So I'm able to replace calls for the actual implementation.

1

ฟังก์ชันแบบอินไลน์

ใน C ++ มาโครไม่มีอะไรนอกจากฟังก์ชันอินไลน์ ตอนนี้มาโครอยู่ภายใต้การควบคุมของคอมไพเลอร์

  • สำคัญ : ถ้าเรากำหนดฟังก์ชันภายในคลาสมันจะกลายเป็นInlineโดยอัตโนมัติ

รหัสของฟังก์ชันอินไลน์จะถูกแทนที่ ณ ตำแหน่งที่เรียกดังนั้นจึงลดค่าใช้จ่ายในการเรียกฟังก์ชัน

ในบางกรณี Inlining of function ไม่สามารถทำงานได้เช่น

  • ถ้าตัวแปรคงที่ใช้ภายในฟังก์ชันอินไลน์

  • หากฟังก์ชันมีความซับซ้อน

  • หากเรียกใช้ฟังก์ชันแบบเรียกซ้ำ

  • หากระบุที่อยู่ของฟังก์ชันโดยปริยายหรืออย่างชัดเจน

ฟังก์ชันที่กำหนดไว้ภายนอกคลาสดังต่อไปนี้อาจกลายเป็นแบบอินไลน์

inline int AddTwoVar(int x,int y); //This may not become inline 

inline int AddTwoVar(int x,int y) { return x + y; } // This becomes inline

ฟังก์ชันที่กำหนดภายในคลาสจะกลายเป็นแบบอินไลน์

// Inline SpeedMeter functions
class SpeedMeter
{
    int speed;
    public:
    int getSpeed() const { return speed; }
    void setSpeed(int varSpeed) { speed = varSpeed; }
};
int main()
{
    SpeedMeter objSM;
    objSM.setSpeed(80);
    int speedValue = A.getSpeed();
} 

ที่นี่ทั้งฟังก์ชัน getSpeed ​​และ setSpeed ​​จะกลายเป็นแบบอินไลน์


เอ๊ะมีข้อมูลที่ดีบางที แต่ไม่ได้จริงๆพยายามที่จะอธิบายว่าทำไม บางทีคุณอาจจะทำ แต่ก็ไม่ได้ทำให้ชัดเจน
thecoshman

2
ข้อความต่อไปนี้ไม่เป็นความจริง: "สำคัญ: หากเรากำหนดฟังก์ชันภายในคลาสฟังก์ชันนั้นจะกลายเป็น Inline โดยอัตโนมัติ" ไม่แม้ว่าคุณจะเขียน "อินไลน์" ในการประกาศ / คำจำกัดความคุณสามารถมั่นใจได้ว่าเป็นอินไลน์ในความเป็นจริง ไม่แม้แต่สำหรับเทมเพลต บางทีคุณอาจหมายความว่าคอมไพเลอร์จะถือว่าคีย์เวิร์ด "อินไลน์" โดยอัตโนมัติ แต่ไม่ต้องปฏิบัติตามและสิ่งที่ฉันสังเกตเห็นก็คือในกรณีส่วนใหญ่มันไม่ได้อยู่ในบรรทัดของคำจำกัดความในส่วนหัวดังกล่าวไม่ใช่แม้แต่สำหรับฟังก์ชัน constexpr ง่ายๆด้วย เลขคณิตพื้นฐาน
Pablo Ariel

เฮ้ขอบคุณสำหรับความคิดเห็น ... ด้านล่างนี้เป็นบรรทัดจากการคิดใน C ++ micc.unifi.it/bertini/download/programmazione/… หน้า 400 .. โปรดตรวจสอบ .. โปรดโหวตว่าคุณเห็นด้วย ขอบคุณ ..... อินไลน์ภายในคลาสในการกำหนดฟังก์ชันอินไลน์คุณจะต้องนำหน้านิยามฟังก์ชันด้วยคีย์เวิร์ดอินไลน์ อย่างไรก็ตามสิ่งนี้ไม่จำเป็นภายในนิยามคลาส ฟังก์ชันใด ๆ ที่คุณกำหนดภายในนิยามคลาสจะเป็นแบบอินไลน์โดยอัตโนมัติ
Saurabh Raoot

ผู้เขียนหนังสือเล่มนั้นสามารถอ้างสิทธิ์ในสิ่งที่ต้องการได้เพราะเขียนหนังสือไม่ใช่เขียนโค้ด นี่คือสิ่งที่ฉันต้องวิเคราะห์อย่างลึกซึ้งเพื่อที่จะทำให้การสาธิต 3 มิติแบบพกพาของฉันมีขนาดที่น้อยกว่า 64kb โดยหลีกเลี่ยงโค้ดอินไลน์ให้มากที่สุด การเขียนโปรแกรมเป็นเรื่องเกี่ยวกับข้อเท็จจริงไม่ใช่ศาสนาดังนั้นจึงไม่สำคัญว่า "โปรแกรมเมอร์พระเจ้า" บางคนกล่าวไว้ในหนังสือหากไม่ได้แสดงถึงสิ่งที่เกิดขึ้นในทางปฏิบัติ และหนังสือ C ++ ส่วนใหญ่จะมีชุดคำแนะนำที่ไม่ดีซึ่งในบางครั้งคุณจะพบเคล็ดลับที่เป็นระเบียบเพื่อเพิ่มในละครของคุณ
Pablo Ariel

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