ตัวแปร constexpr แบบคงที่ภายในฟังก์ชั่นเหมาะสมหรือไม่?


193

ถ้าฉันมีตัวแปรภายในฟังก์ชั่น (พูดเป็นอาร์เรย์ขนาดใหญ่) มันทำให้รู้สึกถึงการประกาศมันทั้งสองstaticและconstexpr? constexprรับประกันได้ว่าอาร์เรย์จะถูกสร้างขึ้นในเวลารวบรวมดังนั้นจะstaticไร้ประโยชน์หรือไม่?

void f() {
    static constexpr int x [] = {
        // a few thousand elements
    };
    // do something with the array
}

เป็นstaticสิ่งที่ทำจริงมีในแง่ของการสร้างรหัสหรือความหมาย?

คำตอบ:


231

คำตอบสั้น ๆ คือไม่เพียง แต่มีstaticประโยชน์เท่านั้น แต่ยังเป็นที่ต้องการอยู่เสมอ

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

ทุกตัวแปรที่ประกาศconstexprมีความหมายโดยนัยconstแต่constและstaticเกือบจะเป็นมุมฉาก (ยกเว้นการโต้ตอบกับstatic constจำนวนเต็ม)

C++รูปแบบวัตถุ (§1.9) ต้องการให้ทุกวัตถุอื่นที่ไม่ใช่บิตเขตครอบครองอย่างน้อยหนึ่งไบต์หน่วยความจำและมีที่อยู่; นอกจากนี้วัตถุดังกล่าวทั้งหมดที่สังเกตได้ในโปรแกรมในช่วงเวลาที่กำหนดจะต้องมีที่อยู่ที่ชัดเจน (วรรค 6) สิ่งนี้ไม่ต้องการให้คอมไพเลอร์สร้างอาเรย์ใหม่บนสแต็กสำหรับทุกการเรียกใช้ของฟังก์ชั่นที่มีอาเรย์ const แบบไม่คงที่ในท้องที่เพราะคอมไพเลอร์สามารถหลบภัยได้ในas-ifหลักการ สังเกต

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

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

ดังนั้นคุณควรใช้static constexprในตัวอย่างของคุณอย่างแน่นอน

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


2
@AndrewLazarus คุณไม่สามารถโยนไปconstจากconstวัตถุเฉพาะจากจุดที่ไปยังconst X* Xแต่ไม่ thats จุด; ประเด็นคือวัตถุอัตโนมัติไม่สามารถมีที่อยู่คงที่ ดังที่ฉันได้กล่าวว่าconstexprจะมีความหมายเมื่อการรวบรวมเสร็จสิ้นดังนั้นจึงไม่มีสิ่งใดที่จะถูกกำจัดทิ้ง (และอาจจะไม่มีอะไรเลยเพราะวัตถุไม่ได้รับประกันว่าจะมีอยู่ในรันไทม์)
rici

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

2
@ void.pointer: คุณพูดถูกเกี่ยวกับย่อหน้าสุดท้าย ฉันเปลี่ยนคำนำ ฉันคิดว่าฉันได้อธิบายถึงความสำคัญของstatic constexpr(มันป้องกันไม่ให้อาร์เรย์คงที่ไม่ต้องถูกสร้างขึ้นใหม่ในทุกการเรียกใช้ฟังก์ชั่น) แต่ฉันปรับแต่งคำบางคำที่อาจทำให้ชัดเจนขึ้น ขอบคุณ
rici

8
อาจเป็นประโยชน์ในการพูดถึงค่าคงที่ของเวลาที่รวบรวมกับค่าคงที่แบบรันไทม์ กล่าวอีกนัยหนึ่งถ้าconstexprตัวแปรคงที่จะใช้เฉพาะในบริบทเวลารวบรวมและไม่จำเป็นต้องใช้เวลาทำงานจึงstaticไม่มีเหตุผลเนื่องจาก ณ จุดที่คุณไปถึงรันไทม์ค่าจะถูก "inlined" อย่างมีประสิทธิภาพ อย่างไรก็ตามหากconstexprใช้ในบริบทรันไทม์ (กล่าวอีกนัยหนึ่งconstexprจะต้องถูกแปลงเป็นconstปริยายและพร้อมใช้งานกับที่อยู่ทางกายภาพสำหรับรหัสรันไทม์) มันจะต้องการความstaticมั่นใจในการปฏิบัติตาม ODR ฯลฯ นั่นคือความเข้าใจของฉันอย่างน้อย
void.pointer

3
ตัวอย่างสำหรับความคิดเห็นล่าสุดของฉัน: static constexpr int foo = 100;. ไม่มีเหตุผลใดที่คอมไพเลอร์ไม่สามารถทดแทนการใช้งานไม่เป็นfooทุกตัวอักษรสำหรับเว้นแต่รหัสกำลังทำสิ่งที่ชอบ100 &fooดังนั้นstaticในวันที่fooไม่มีประโยชน์ในกรณีนี้เนื่องจากfooไม่ได้อยู่ที่รันไทม์ อีกครั้งทั้งหมดขึ้นอยู่กับคอมไพเลอร์
void.pointer

10

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

รหัสต่อไปนี้แสดงให้เห็นถึงวิธีการconstexprเริ่มต้นตัวแปรหลายครั้ง (ด้วยค่าเดียวกันแม้ว่า) ในขณะที่static constexprแน่นอนเริ่มต้นเพียงครั้งเดียวเท่านั้น

นอกจากนี้รหัสเปรียบเทียบประโยชน์จากconstexprกับร่วมกับconststatic

#include <iostream>
#include <string>
#include <cassert>
#include <sstream>

const short const_short = 0;
constexpr short constexpr_short = 0;

// print only last 3 address value numbers
const short addr_offset = 3;

// This function will print name, value and address for given parameter
void print_properties(std::string ref_name, const short* param, short offset)
{
    // determine initial size of strings
    std::string title = "value \\ address of ";
    const size_t ref_size = ref_name.size();
    const size_t title_size = title.size();
    assert(title_size > ref_size);

    // create title (resize)
    title.append(ref_name);
    title.append(" is ");
    title.append(title_size - ref_size, ' ');

    // extract last 'offset' values from address
    std::stringstream addr;
    addr << param;
    const std::string addr_str = addr.str();
    const size_t addr_size = addr_str.size();
    assert(addr_size - offset > 0);

    // print title / ref value / address at offset
    std::cout << title << *param << " " << addr_str.substr(addr_size - offset) << std::endl;
}

// here we test initialization of const variable (runtime)
void const_value(const short counter)
{
    static short temp = const_short;
    const short const_var = ++temp;
    print_properties("const", &const_var, addr_offset);

    if (counter)
        const_value(counter - 1);
}

// here we test initialization of static variable (runtime)
void static_value(const short counter)
{
    static short temp = const_short;
    static short static_var = ++temp;
    print_properties("static", &static_var, addr_offset);

    if (counter)
        static_value(counter - 1);
}

// here we test initialization of static const variable (runtime)
void static_const_value(const short counter)
{
    static short temp = const_short;
    static const short static_var = ++temp;
    print_properties("static const", &static_var, addr_offset);

    if (counter)
        static_const_value(counter - 1);
}

// here we test initialization of constexpr variable (compile time)
void constexpr_value(const short counter)
{
    constexpr short constexpr_var = constexpr_short;
    print_properties("constexpr", &constexpr_var, addr_offset);

    if (counter)
        constexpr_value(counter - 1);
}

// here we test initialization of static constexpr variable (compile time)
void static_constexpr_value(const short counter)
{
    static constexpr short static_constexpr_var = constexpr_short;
    print_properties("static constexpr", &static_constexpr_var, addr_offset);

    if (counter)
        static_constexpr_value(counter - 1);
}

// final test call this method from main()
void test_static_const()
{
    constexpr short counter = 2;

    const_value(counter);
    std::cout << std::endl;

    static_value(counter);
    std::cout << std::endl;

    static_const_value(counter);
    std::cout << std::endl;

    constexpr_value(counter);
    std::cout << std::endl;

    static_constexpr_value(counter);
    std::cout << std::endl;
}

ผลลัพธ์ของโปรแกรมที่เป็นไปได้:

value \ address of const is               1 564
value \ address of const is               2 3D4
value \ address of const is               3 244

value \ address of static is              1 C58
value \ address of static is              1 C58
value \ address of static is              1 C58

value \ address of static const is        1 C64
value \ address of static const is        1 C64
value \ address of static const is        1 C64

value \ address of constexpr is           0 564
value \ address of constexpr is           0 3D4
value \ address of constexpr is           0 244

value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0

ตามที่คุณเห็นว่าตัวเองconstexprเริ่มต้นหลายครั้ง (ที่อยู่ไม่เหมือนกัน) ในขณะที่staticคำหลักทำให้มั่นใจได้ว่าการเริ่มต้นจะดำเนินการเพียงครั้งเดียว


เราไม่สามารถใช้constexpr const short constexpr_shortสำหรับการให้ข้อผิดพลาดได้หรือไม่หากconstexpr_short ได้รับการเริ่มต้นใหม่อีกครั้ง
akhileshzmishra

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