ตัวแปรอินไลน์ทำงานอย่างไร?


124

ในการประชุมมาตรฐาน Oulu ISO C ++ 2016 ข้อเสนอที่เรียกว่าInline Variablesได้รับการโหวตให้เป็น C ++ 17 โดยคณะกรรมการมาตรฐาน

ในแง่ของคนธรรมดาตัวแปรอินไลน์คืออะไรทำงานอย่างไรและมีประโยชน์อะไร ควรประกาศกำหนดและใช้ตัวแปรอินไลน์อย่างไร?


@jotik ฉันเดาว่าการดำเนินการที่เทียบเท่าจะแทนที่การเกิดขึ้นของตัวแปรด้วยค่าของมัน constปกตินี้จะใช้ได้เฉพาะถ้าตัวแปรคือ
melpomene

5
นั่นไม่ใช่สิ่งเดียวที่inlineคีย์เวิร์ดใช้กับฟังก์ชัน inlineคำหลักเมื่อนำไปใช้ฟังก์ชั่นมีผลกระทบที่สำคัญอีกคนหนึ่งซึ่งแปลโดยตรงกับตัวแปร inlineฟังก์ชั่นที่สันนิษฐานประกาศในไฟล์ส่วนหัวจะไม่ส่งผลในสัญลักษณ์ "ซ้ำ" ข้อผิดพลาดในเวลาที่เชื่อมโยงถึงแม้ว่าส่วนหัวที่ได้รับ#includeวันที่หน่วยการแปลหลาย inlineคำหลักเมื่อนำไปใช้กับตัวแปรที่จะมีผลแน่นอนเดียวกัน ตอนจบ.
Sam Varshavchik

4
^ ในความหมายของ 'แทนที่การเรียกใช้ฟังก์ชันนี้ด้วยสำเนาโค้ดแทน' inlineเป็นเพียงคำขอที่อ่อนแอและไม่ผูกมัดกับเครื่องมือเพิ่มประสิทธิภาพ คอมไพเลอร์มีอิสระที่จะไม่ใช้ฟังก์ชันที่ร้องขอแบบอินไลน์และ / หรืออินไลน์ที่คุณไม่ได้ใส่คำอธิบายประกอบ แต่จุดประสงค์ที่แท้จริงของinlineคำหลักคือเพื่อหลีกเลี่ยงข้อผิดพลาดของคำจำกัดความหลายข้อ
underscore_d

คำตอบ:


121

ประโยคแรกของข้อเสนอ:

inlineระบุสามารถนำไปใช้กับตัวแปรเช่นเดียวกับฟังก์ชั่น

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

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

จนถึงและรวมถึง C ++ 14 เครื่องจักรภายในสำหรับสิ่งนี้เพื่อรองรับstaticตัวแปรในเทมเพลตคลาส แต่ไม่มีวิธีที่สะดวกในการใช้เครื่องจักรนั้น หนึ่งต้องใช้เทคนิคเช่น

template< class Dummy >
struct Kath_
{
    static std::string const hi;
};

template< class Dummy >
std::string const Kath_<Dummy>::hi = "Zzzzz...";

using Kath = Kath_<void>;    // Allows you to write `Kath::hi`.

ตั้งแต่ C ++ 17 เป็นต้นไปฉันเชื่อว่าใคร ๆ ก็เขียนได้

struct Kath
{
    static std::string const hi;
};

inline std::string const Kath::hi = "Zzzzz...";    // Simpler!

…ในไฟล์ส่วนหัว

ข้อเสนอรวมถึงถ้อยคำ

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

…ซึ่งช่วยให้ข้างต้นง่ายขึ้นไปอีกเพียงแค่

struct Kath
{
    static inline std::string const hi = "Zzzzz...";    // Simplest!
};

…ตามที่ TC ระบุไว้ในความคิดเห็นต่อคำตอบนี้

นอกจากนี้ตัว  ​constexprระบุยังหมายถึง  inline สมาชิกข้อมูลแบบคงที่และฟังก์ชัน


หมายเหตุ:
¹สำหรับฟังก์ชันinlineยังมีผลเป็นนัยเกี่ยวกับการปรับให้เหมาะสมคอมไพลเลอร์ควรแทนที่การเรียกของฟังก์ชันนี้ด้วยการแทนที่รหัสเครื่องของฟังก์ชันโดยตรง คำใบ้นี้สามารถละเว้นได้


2
นอกจากนี้ข้อ จำกัด const ยังใช้กับตัวแปรขอบเขตเนมสเปซเท่านั้น ขอบเขตคลาส (เช่นKath::hi) ไม่จำเป็นต้องเป็น const
TC

4
รายงานที่ใหม่กว่าระบุว่าconstข้อ จำกัด ถูกยกเลิกทั้งหมด
TC

2
@Nick: เนื่องจาก Richard Smith ("บรรณาธิการโครงการ" ของคณะกรรมการ C ++ ในปัจจุบันเป็นหนึ่งในผู้เขียนสองคนและเนื่องจากเขาเป็น "เจ้าของรหัสของส่วนหน้า Clang C ++" จึงเดาเสียงดังลั่น และสร้างที่รวบรวมกับเสียงดังกราว 3.9.0 มากกว่าที่Godbolt เตือนว่าตัวแปรอินไลน์เป็นส่วนขยาย C ++ 1z ฉันไม่พบวิธีแชร์ตัวเลือกและตัวเลือกซอร์สและคอมไพเลอร์ดังนั้นลิงก์จึงไปยังไซต์โดยทั่วไปขออภัย
ไชโยและ hth - Alf

1
ทำไมต้องใช้คำหลักแบบอินไลน์ภายในการประกาศคลาส / โครงสร้าง ทำไมไม่ยอมง่ายๆstatic std::string const hi = "Zzzzz...";?
sasha.sochka

2
@EmilianCioca: ไม่มีคุณจะวิ่งปะทะกันของความล้มเหลวแบบคงที่เพื่อเริ่มต้น ซิงเกิลตันเป็นอุปกรณ์ที่หลีกเลี่ยงสิ่งนั้นเป็นหลัก
ไชโยและ hth - Alf

15

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

ตัวแปรอินไลน์สามารถใช้เพื่อกำหนด globals ในไลบรารีส่วนหัวเท่านั้น ก่อน C ++ 17 พวกเขาต้องใช้วิธีแก้ปัญหา (ฟังก์ชันอินไลน์หรือแฮ็กเทมเพลต)

ตัวอย่างเช่นวิธีแก้ปัญหาอย่างหนึ่งคือการใช้Singleton ของ Meyerกับฟังก์ชันอินไลน์:

inline T& instance()
{
  static T global;
  return global;
}

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

ด้วยตัวแปรอินไลน์คุณสามารถประกาศได้โดยตรง (โดยไม่ได้รับข้อผิดพลาดตัวเชื่อมโยงคำจำกัดความหลายคำ):

inline T global;

นอกเหนือจากไลบรารีส่วนหัวเท่านั้นยังมีกรณีอื่น ๆ ที่ตัวแปรอินไลน์สามารถช่วยได้ Nir ฟรีดแมนครอบคลุมหัวข้อนี้ในการพูดคุยของเขาที่ CppCon: อะไร c ++ นักพัฒนาควรรู้เกี่ยวกับ Globals (และลิงเกอร์) ส่วนที่เกี่ยวกับตัวแปรแบบอินไลน์และการแก้ไขปัญหาเริ่มต้นที่ 18m9s

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

(ยังคงมีกรณีการใช้งานสำหรับซิงเกิลตันของเมเยอร์เช่นหากคุณต้องการเริ่มต้นแบบขี้เกียจอย่างชัดเจน)


11

ตัวอย่างที่รันได้น้อยที่สุด

คุณลักษณะ C ++ 17 ที่ยอดเยี่ยมนี้ช่วยให้เราสามารถ:

  • ใช้เพียงที่อยู่หน่วยความจำเดียวสำหรับค่าคงที่แต่ละค่า
  • จัดเก็บเป็นconstexpr: จะประกาศ constexpr extern ได้อย่างไร?
  • ทำในบรรทัดเดียวจากส่วนหัวเดียว

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

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

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub อัปสตรี

ดูเพิ่มเติม: ตัวแปรอินไลน์ทำงานอย่างไร

มาตรฐาน C ++ สำหรับตัวแปรอินไลน์

มาตรฐาน C ++ รับประกันว่าที่อยู่จะเหมือนกัน C ++ 17 N4659 ร่างมาตรฐาน 10.1.6 "ตัวระบุอินไลน์":

6 ฟังก์ชันอินไลน์หรือตัวแปรที่มีการเชื่อมโยงภายนอกต้องมีที่อยู่เดียวกันในทุกหน่วยการแปล

cppreference https://en.cppreference.com/w/cpp/language/inlineอธิบายว่าหากstaticไม่ได้รับแสดงว่ามีการเชื่อมโยงภายนอก

การใช้งานตัวแปรแบบอินไลน์ของ GCC

เราสามารถสังเกตวิธีการใช้งานได้ด้วย:

nm main.o notmain.o

ซึ่งประกอบด้วย:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

และman nmพูดเกี่ยวกับu:

"u" สัญลักษณ์นี้เป็นสัญลักษณ์สากลที่เป็นเอกลักษณ์ นี่คือส่วนขยาย GNU ของชุดมาตรฐานของการผูกสัญลักษณ์ ELF สำหรับสัญลักษณ์ดังกล่าวตัวเชื่อมโยงแบบไดนามิกจะตรวจสอบให้แน่ใจว่าในกระบวนการทั้งหมดมีเพียงสัญลักษณ์เดียวที่มีชื่อนี้และพิมพ์ที่ใช้

ดังนั้นเราจึงเห็นว่ามีส่วนขยาย ELF สำหรับสิ่งนี้โดยเฉพาะ

ก่อน C ++ 17: extern const

ก่อน C ++ 17 และใน C เราสามารถบรรลุผลที่คล้ายกันมากกับ an extern constซึ่งจะนำไปสู่การใช้ตำแหน่งหน่วยความจำเดียว

ข้อเสียinlineคือ:

  • มันเป็นไปไม่ได้ที่จะสร้างตัวแปรconstexprด้วยเทคนิคนี้inlineอนุญาตให้ทำได้เท่านั้น: จะประกาศ constexpr extern ได้อย่างไร?
  • มีความสง่างามน้อยกว่าเนื่องจากคุณต้องประกาศและกำหนดตัวแปรแยกกันในไฟล์ header และ cpp

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHub อัปสตรี

Pre-C ++ 17 ส่วนหัวทางเลือกเท่านั้น

สิ่งเหล่านี้ไม่ดีเท่าexternวิธีแก้ปัญหา แต่ใช้งานได้และใช้ตำแหน่งหน่วยความจำเดียวเท่านั้น:

constexprฟังก์ชั่นเพราะconstexprหมายถึงinlineและinline ช่วย (กองกำลัง) คำนิยามที่ปรากฏในหน่วยการแปลทุก :

constexpr int shared_inline_constexpr() { return 42; }

และฉันพนันได้เลยว่าคอมไพเลอร์ที่เหมาะสมจะอินไลน์

คุณยังสามารถใช้ตัวแปรจำนวนเต็มconstหรือconstexprคงที่ใน:

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

แต่คุณไม่สามารถทำสิ่งต่างๆได้เช่นการรับที่อยู่มิฉะนั้นจะกลายเป็นสิ่งที่ใช้ไม่ได้โปรดดู: https://en.cppreference.com/w/cpp/language/static "Constant static members" และการกำหนดข้อมูลคงที่ constexpr สมาชิก

ใน C สถานการณ์จะเหมือนกับ C ++ ก่อน C ++ 17 ฉันได้อัปโหลดตัวอย่างที่: "คงที่" หมายความว่าอะไรใน C?

ข้อแตกต่างเพียงอย่างเดียวก็คือใน C ++ มีconstความหมายstaticสำหรับ globals แต่ไม่ได้อยู่ในความหมายของ C: C ++ ของ "static const" เทียบกับ "const"

มีวิธีใดที่จะอินไลน์ได้อย่างเต็มที่?

สิ่งที่ต้องทำ: มีวิธีใดบ้างในการแทรกตัวแปรโดยไม่ต้องใช้หน่วยความจำเลย?

เหมือนกับสิ่งที่พรีโปรเซสเซอร์ทำ

สิ่งนี้จะต้องมี:

  • ห้ามหรือตรวจจับว่ามีการใช้แอดเดรสของตัวแปรหรือไม่
  • เพิ่มข้อมูลนั้นในไฟล์ออบเจ็กต์ ELF และปล่อยให้ LTO ปรับให้เหมาะสม

ที่เกี่ยวข้อง:

ทดสอบใน Ubuntu 18.10, GCC 8.2.0


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