เหตุใดฉันจึงไม่สามารถเริ่มต้นสมาชิกแบบคงที่ที่ไม่ใช่ const หรืออาร์เรย์แบบคงที่ในคลาสได้


116

เหตุใดฉันจึงไม่สามารถเริ่มต้นstaticสมาชิกที่ไม่ใช่ const หรือstaticอาร์เรย์ในคลาสได้

class A
{
    static const int a = 3;
    static int b = 3;
    static const int c[2] = { 1, 2 };
    static int d[2] = { 1, 2 };
};

int main()
{
    A a;

    return 0;
}

คอมไพเลอร์ออกข้อผิดพลาดต่อไปนี้:

g++ main.cpp
main.cpp:4:17: error: ISO C++ forbids in-class initialization of non-const static member b
main.cpp:5:26: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:5:33: error: invalid in-class initialization of static data member of non-integral type const int [2]’
main.cpp:6:20: error: a brace-enclosed initializer is not allowed here before ‘{’ token
main.cpp:6:27: error: invalid in-class initialization of static data member of non-integral type int [2]’

ฉันมีสองคำถาม:

  1. เหตุใดฉันจึงไม่สามารถเริ่มต้นstaticสมาชิกข้อมูลในชั้นเรียนได้
  2. เหตุใดฉันจึงไม่สามารถเริ่มต้นstaticอาร์เรย์ในชั้นเรียนได้แม้กระทั่งconstอาร์เรย์

1
ฉันคิดว่าเหตุผลหลักคือการทำให้ถูกต้องเป็นเรื่องยุ่งยาก โดยหลักการแล้วคุณสามารถทำในสิ่งที่คุณกำลังพูดถึงได้ แต่จะมีผลข้างเคียงแปลก ๆ เช่นถ้าตัวอย่างอาร์เรย์ของคุณได้รับอนุญาตคุณอาจได้รับค่า A :: c [0] แต่ไม่สามารถส่ง A :: c ไปยังฟังก์ชันได้เนื่องจากจะต้องใช้แอดเดรสและเวลาคอมไพล์ ค่าคงที่ไม่มีที่อยู่ C ++ 11 ได้เปิดใช้งานบางส่วนโดยใช้ constexpr
Vaughn Cato

คำถามที่ดีและเป็นคำตอบ ลิงก์ที่ช่วยฉัน: msdn.microsoft.com/en-us/library/0e5kx78b.aspx
ETFovac

คำตอบ:


144

เหตุใดฉันจึงไม่สามารถเริ่มต้นstaticสมาชิกข้อมูลในชั้นเรียนได้

มาตรฐาน C ++ อนุญาตให้มีการเตรียมใช้งานเฉพาะประเภทอินทิกรัลค่าคงที่คงที่หรือประเภทการแจงนับเท่านั้น นี่คือเหตุผลที่aอนุญาตให้เริ่มต้นในขณะที่คนอื่นไม่ได้

อ้างอิง:
C ++ 03 9.4.2 สมาชิกข้อมูลคงที่
§4

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

ประเภทอินทิกรัลคืออะไร?

C ++ 03 3.9.1 ประเภทพื้นฐาน
§7

ประเภท bool, char, wchar_t และประเภทจำนวนเต็มที่ลงชื่อและไม่ได้ลงนามเรียกรวมกันว่าประเภทอินทิกรัล 43) คำพ้องความหมายสำหรับประเภทอินทิกรัลคือประเภทจำนวนเต็ม

เชิงอรรถ:

43)ดังนั้นการแจงนับ (7.2) จึงไม่เป็นส่วนประกอบ อย่างไรก็ตามการแจงนับสามารถเลื่อนระดับเป็น int ไม่ได้ลงนาม int ยาวหรือไม่ได้ลงนามยาวตามที่ระบุไว้ใน 4.5

วิธีแก้ปัญหา:

คุณสามารถใช้เคล็ดลับ enumเพื่อเริ่มต้นอาร์เรย์ภายในนิยามคลาสของคุณ

class A 
{
    static const int a = 3;
    enum { arrsize = 2 };

    static const int c[arrsize] = { 1, 2 };

};

เหตุใดมาตรฐานจึงไม่อนุญาตให้ทำเช่นนี้

Bjarne อธิบายสิ่งนี้อย่างเหมาะสมที่นี่ :

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

เหตุใดstatic constประเภทอินทิกรัลและ enums เท่านั้นที่อนุญาตการเริ่มต้นในคลาส

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

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

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

enums ได้รับอนุญาตเนื่องจากสามารถใช้ค่าของชนิดที่แจกแจงได้ในกรณีที่คาดว่า ints ดูการอ้างอิงด้านบน


สิ่งนี้เปลี่ยนแปลงอย่างไรใน C ++ 11?

C ++ 11 ผ่อนคลายข้อ จำกัด ในระดับหนึ่ง

C ++ 11 9.4.2 สมาชิกข้อมูลคงที่
§3

ถ้าสมาชิกข้อมูลแบบคงที่เป็นชนิด const literal การประกาศในนิยามคลาสสามารถระบุbrace-or-equal-initializerซึ่งทุกinitializer-clauseที่เป็นนิพจน์ที่กำหนดเป็นนิพจน์คงที่ สมาชิกข้อมูลแบบคงที่ของประเภทตัวอักษรสามารถประกาศได้ในนิยามคลาสด้วยconstexpr specifier;ถ้าเป็นเช่นนั้นการประกาศจะต้องระบุตัวเริ่มต้นแบบรั้งหรือเท่ากันซึ่งทุกตัวเริ่มต้น - ประโยคที่เป็นนิพจน์การกำหนดคือนิพจน์คงที่ [หมายเหตุ: ในทั้งสองกรณีนี้สมาชิกอาจปรากฏในนิพจน์คงที่ —end หมายเหตุ] สมาชิกจะยังคงถูกกำหนดในขอบเขตเนมสเปซหากใช้ในโปรแกรมและข้อกำหนดขอบเขตเนมสเปซจะต้องไม่มีตัวเริ่มต้น

นอกจากนี้ C ++ 11 จะอนุญาตให้ (§12.6.2.8) สมาชิกข้อมูลที่ไม่คงที่สามารถเริ่มต้นได้เมื่อมีการประกาศ (ในคลาส) ซึ่งจะหมายถึงความหมายของผู้ใช้ที่ง่ายมาก

โปรดทราบว่าคุณลักษณะเหล่านี้ยังไม่ได้ใช้ใน gcc 4.7 ล่าสุดดังนั้นคุณอาจยังคงได้รับข้อผิดพลาดในการคอมไพล์


7
สิ่งต่าง ๆ ใน c ++ 11 คำตอบสามารถใช้การอัปเดต
bames53

4
สิ่งนี้ดูเหมือนจะไม่เป็นความจริง: "โปรดทราบว่ามีเพียงจำนวนเต็ม const คงที่เท่านั้นที่สามารถถือว่าเป็นค่าคงที่ของเวลาคอมไพล์ได้คอมไพเลอร์รู้ว่าค่าจำนวนเต็มจะไม่เปลี่ยนแปลงตลอดเวลาดังนั้นจึงสามารถใช้เวทมนตร์ของตัวเองและใช้การปรับให้เหมาะสมได้คอมไพเลอร์ก็เพียง แทรกสมาชิกชั้นเรียนเช่นพวกเขาจะไม่ถูกเก็บไว้ในหน่วยความจำอีกต่อไป " แน่ใจหรือไม่ว่าพวกเขาไม่จำเป็นต้องเก็บไว้ในหน่วยความจำ? จะเป็นอย่างไรหากฉันให้คำจำกัดความสำหรับสมาชิก อะไรจะ&memberกลับมา?
Nawaz

2
@ แอล: ใช่. นั่นคือสิ่งที่คำถามของฉันคือ เหตุใด C ++ จึงอนุญาตการเริ่มต้นในคลาสสำหรับประเภทอินทิกรัลเท่านั้นจึงไม่ได้รับคำตอบที่ถูกต้องจากคำตอบของคุณ คิดว่าเหตุใดจึงไม่อนุญาตให้เริ่มต้นสำหรับstatic const char*สมาชิก?
Nawaz

3
@Nawaz: เนื่องจาก C ++ 03 อนุญาตเฉพาะค่าเริ่มต้นคงที่สำหรับประเภทการแจกแจงแบบคงที่และแบบอินทิกรัลและ const เท่านั้นและไม่มีประเภทอื่น C ++ 11 จึงขยายสิ่งนี้เป็นประเภทตัวอักษร constซึ่งจะช่วยผ่อนคลายบรรทัดฐานสำหรับการเริ่มต้นในคลาส ใน C ++ 03 อาจเป็นการกำกับดูแลซึ่งรับประกันการเปลี่ยนแปลงและด้วยเหตุนี้จึงได้รับการแก้ไขใน C ++ 11 หากมีเหตุผลทางยุทธวิธีแบบดั้งเดิมสำหรับการเปลี่ยนแปลงฉันไม่ทราบหากคุณทราบถึงสิ่งใด ๆ อย่าลังเลที่จะแบ่งปัน พวกเขา
Alok Save

4
"วิธีแก้ปัญหา" พาร์ที่คุณกล่าวถึงไม่ทำงานกับ g ++
iammilind

4

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

// header.hxx
#include <vector>

class Class {
public:
    static std::vector<int> & replacement_for_initialized_static_non_const_variable() {
        static std::vector<int> Static {42, 0, 1900, 1998};
        return Static;
    }
};

int compilation_unit_a();

และ

// compilation_unit_a.cxx
#include "header.hxx"

int compilation_unit_a() {  
    return Class::replacement_for_initialized_static_non_const_variable()[1]++;
}

และ

// main.cxx
#include "header.hxx"

#include <iostream>

int main() {
    std::cout
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << compilation_unit_a()
    << Class::replacement_for_initialized_static_non_const_variable()[1]++
    << std::endl;
}

สร้าง:

g++ -std=gnu++0x -save-temps=obj -c compilation_unit_a.cxx 
g++ -std=gnu++0x -o main main.cxx compilation_unit_a.o

วิ่ง:

./main

ความจริงที่ว่าสิ่งนี้ใช้งานได้ (อย่างสม่ำเสมอแม้ว่านิยามคลาสจะรวมอยู่ในหน่วยการคอมไพล์ที่แตกต่างกัน) แสดงให้เห็นว่าตัวเชื่อมโยงในวันนี้ (gcc 4.9.2) นั้นฉลาดพอจริงๆ

ตลก: พิมพ์0123ที่แขนและ3210บน x86


1

ฉันคิดว่ามันเพื่อป้องกันไม่ให้คุณผสมคำประกาศและคำจำกัดความ (ลองนึกถึงปัญหาที่อาจเกิดขึ้นหากคุณรวมไฟล์ไว้ในหลาย ๆ ที่)


0

เนื่องจากสามารถมีคำจำกัดความได้เพียงคำเดียวเท่านั้นA::aที่หน่วยการแปลทั้งหมดใช้

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

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

สิ่งหนึ่งที่ควรทราบคือstatic inline int b;ถือว่าเป็นคำจำกัดความในขณะที่static const int bหรือstatic const A b;ยังคงถือเป็นคำประกาศและต้องกำหนดไว้นอกบรรทัดหากคุณไม่ได้กำหนดไว้ในคลาส สิ่งที่น่าสนใจstatic constexpr A b;คือถือว่าเป็นคำจำกัดความในขณะstatic constexpr int b;ที่ข้อผิดพลาดและต้องมีตัวเริ่มต้น (เนื่องจากตอนนี้กลายเป็นคำจำกัดความและเหมือนกับคำจำกัดความ const / constexpr ใด ๆ ที่ขอบเขตไฟล์พวกเขาต้องการตัวเริ่มต้นที่ int ไม่มี แต่เป็นประเภทคลาส ทำเพราะมีนัย= A()เมื่อเป็นคำจำกัดความ - เสียงดังกราวอนุญาต แต่ gcc ต้องการให้คุณเริ่มต้นอย่างชัดเจนหรือเป็นข้อผิดพลาดนี่ไม่ใช่ปัญหากับอินไลน์แทน) static const A b = A();ไม่ได้รับอนุญาตและต้องเป็นconstexprหรือinlineเพื่อที่จะอนุญาตให้ผู้เริ่มต้นสำหรับวัตถุคงที่ที่มีประเภทคลาสคือทำให้สมาชิกคงที่ของประเภทคลาสมากกว่าการประกาศ ใช่ในบางสถานการณ์A a;จะไม่เหมือนกับการเริ่มต้นอย่างชัดเจนA a = A();(ก่อนหน้านี้อาจเป็นการประกาศ แต่ถ้าอนุญาตเฉพาะการประกาศสำหรับประเภทนั้นคำสั่งหลังจะเป็นข้อผิดพลาดคำจำกัดความหลังสามารถใช้ได้เฉพาะกับคำจำกัดความเท่านั้นที่constexprทำให้เป็นคำจำกัดความ ). หากคุณใช้constexprและระบุตัวสร้างเริ่มต้นตัวสร้างจะต้องเป็นconstexpr

#include<iostream>

struct A
{
    int b =2;
    mutable int c = 3; //if this member is included in the class then const A will have a full .data symbol emitted for it on -O0 and so will B because it contains A.
    static const int a = 3;
};

struct B {
    A b;
    static constexpr A c; //needs to be constexpr or inline and doesn't emit a symbol for A a mutable member on any optimisation level
};

const A a;
const B b;

int main()
{
    std::cout << a.b << b.b.b;
    return 0;
}

สมาชิกแบบคงที่คือการประกาศขอบเขตไฟล์ทันทีextern int A::a;(ซึ่งสามารถทำได้ในคลาสเท่านั้นและคำจำกัดความนอกบรรทัดต้องอ้างถึงสมาชิกแบบคงที่ในคลาสและต้องเป็นคำจำกัดความและไม่สามารถมีภายนอกได้) ในขณะที่สมาชิกที่ไม่คงที่เป็นส่วนหนึ่งของ externความหมายประเภทที่สมบูรณ์ของชั้นเรียนและมีกฎระเบียบเช่นเดียวกับการประกาศขอบเขตไฟล์โดยไม่ต้อง เป็นคำจำกัดความโดยปริยาย ดังนั้นจึงint i[]; int i[5];เป็นการกำหนดนิยามใหม่ในขณะที่static int i[]; int A::i[5];ไม่ได้แตกต่างจาก 2 externs คอมไพลเลอร์จะยังตรวจพบสมาชิกที่ซ้ำกันหากคุณทำstatic int i[]; static int i[5];ในคลาส


-3

ตัวแปรคงเป็นตัวแปรเฉพาะสำหรับคลาส ตัวสร้างเริ่มต้นแอตทริบิวต์โดยเฉพาะสำหรับอินสแตนซ์

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