การกำหนดสมาชิกคงที่ const จำนวนเต็มในนิยามคลาส


109

ความเข้าใจของฉันคือ C ++ อนุญาตให้กำหนดสมาชิก const แบบคงที่ภายในคลาสได้ตราบใดที่เป็นประเภทจำนวนเต็ม

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

#include <algorithm>
#include <iostream>

class test
{
public:
    static const int N = 10;
};

int main()
{
    std::cout << test::N << "\n";
    std::min(9, test::N);
}

ข้อผิดพลาดที่ฉันได้รับคือ:

test.cpp:(.text+0x130): undefined reference to `test::N'
collect2: ld returned 1 exit status

ที่น่าสนใจคือถ้าฉันแสดงความคิดเห็นเกี่ยวกับการเรียกไปที่ std :: min โค้ดจะคอมไพล์และลิงก์ได้ดี (แม้ว่า test :: N จะอ้างอิงในบรรทัดก่อนหน้าด้วย)

มีความคิดว่าเกิดอะไรขึ้น?

คอมไพเลอร์ของฉันคือ gcc 4.4 บน Linux


3
ทำงานได้ดีบน Visual Studio 2010
Puppy

4
ข้อผิดพลาดที่แน่นอนนี้อธิบายได้ที่gcc.gnu.org/wiki/…
Jonathan Wakely

ในกรณีที่โดยเฉพาะอย่างยิ่งของคุณสามารถกำหนดมันแทนเป็นchar constexpr static const char &N = "n"[0];หมายเหตุ&. ฉันเดาว่ามันใช้งานได้เพราะสตริงตามตัวอักษรถูกกำหนดโดยอัตโนมัติ ฉันค่อนข้างกังวลเกี่ยวกับเรื่องนี้ - มันอาจทำงานผิดปกติในไฟล์ส่วนหัวระหว่างหน่วยการแปลที่แตกต่างกันเนื่องจากสตริงอาจอยู่ในที่อยู่ที่แตกต่างกันหลายรายการ
Aaron McDaid

1
คำถามนี้เป็นการแสดงให้เห็นว่าคำตอบ C ++ สำหรับ "ไม่ใช้ #defines สำหรับค่าคงที่" แย่เพียงใด
Johannes Overmann

1
@JohannesOvermann ในเรื่องนี้ฉันต้องการพูดถึงการใช้อินไลน์สำหรับตัวแปรส่วนกลางตั้งแต่ C ++ 17 inline const int N = 10ซึ่งสำหรับความรู้ของฉันยังมีที่เก็บข้อมูลที่กำหนดโดยตัวเชื่อมโยง นอกจากนี้ยังสามารถใช้คีย์เวิร์ดอินไลน์ในกรณีนี้เพื่อให้นิยามตัวแปรคงที่ภายในการทดสอบนิยามคลาส
Wormer

คำตอบ:


72

ความเข้าใจของฉันคือ C ++ อนุญาตให้กำหนดสมาชิก const แบบคงที่ภายในคลาสได้ตราบใดที่เป็นประเภทจำนวนเต็ม

คุณเรียงลำดับถูกต้อง คุณได้รับอนุญาตให้เริ่มต้นอินทิกรัล const แบบคงที่ในการประกาศคลาส แต่นั่นไม่ใช่นิยาม

http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr038.htm

ที่น่าสนใจคือถ้าฉันแสดงความคิดเห็นเกี่ยวกับการเรียกไปที่ std :: min โค้ดจะคอมไพล์และลิงก์ได้ดี (แม้ว่า test :: N จะอ้างอิงในบรรทัดก่อนหน้าด้วย)

มีความคิดว่าเกิดอะไรขึ้น?

std :: min ใช้พารามิเตอร์โดยการอ้างอิง const ถ้าเอาตามมูลค่าคุณจะไม่มีปัญหานี้ แต่เนื่องจากคุณต้องการข้อมูลอ้างอิงคุณจึงต้องมีคำจำกัดความด้วย

นี่คือบท / กลอน:

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

ดูคำตอบของ Chu สำหรับวิธีแก้ปัญหาที่เป็นไปได้


ฉันเห็นว่ามันน่าสนใจ ในกรณีนั้นอะไรคือความแตกต่างระหว่างการให้ค่า ณ จุดประกาศกับการให้ค่าที่จุดนิยาม? มีตัวไหนแนะนำ
HighCommander4

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

2
คำตอบสั้น ๆ คือคงที่ const x = 1; เป็น rvalue แต่ไม่ใช่ lvalue ค่านี้มีให้เป็นค่าคงที่ ณ เวลาคอมไพล์ (คุณสามารถกำหนดขนาดอาร์เรย์ได้) คงที่ y; ต้องกำหนด [no initializer] ในไฟล์ cpp และอาจใช้เป็น rvalue หรือ lvalue
Dale Wilson

2
คงจะดีไม่น้อยหากพวกเขาสามารถขยาย / ปรับปรุงสิ่งนี้ได้ ในความคิดของฉันวัตถุที่เริ่มต้น แต่ไม่ได้กำหนดควรได้รับการปฏิบัติเช่นเดียวกับตัวอักษร ตัวอย่างเช่นเราได้รับอนุญาตให้ผูกลิเทอรัล5กับไฟล์const int&. แล้วทำไมไม่ถือว่า OP test::Nเป็นตัวอักษรที่สอดคล้องกัน?
Aaron McDaid

คำอธิบายที่น่าสนใจขอบคุณ! ซึ่งหมายความว่าใน C ++ static const int ยังไม่มีการแทนที่จำนวนเต็ม #defines enum จะเซ็นชื่อเฉพาะ int เท่านั้นดังนั้นจึงต้องใช้คลาส enum สำหรับค่าคงที่แต่ละตัว มันค่อนข้างชัดเจนสำหรับฉันที่จะลดการประกาศค่าคงที่ด้วยค่าคงที่และรู้ค่าเป็นค่าคงที่ตามตัวอักษรซึ่งวิธีนี้จะรวบรวมได้โดยไม่มีปัญหา C ++ ไปได้ไกล ...
Johannes Overmann

51

ตัวอย่างของ Bjarne Stroustrup ในคำถามที่พบบ่อยเกี่ยวกับ C ++ ของเขาแสดงว่าคุณถูกต้องและต้องการคำจำกัดความก็ต่อเมื่อคุณใช้ที่อยู่

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

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


2
std::minใช้พารามิเตอร์โดยการอ้างอิงซึ่งเป็นเหตุผลว่าทำไมจึงต้องมีคำจำกัดความ
Rakete1111

ฉันจะเขียนคำจำกัดความได้อย่างไรถ้า AE เป็นเทมเพลตคลาส AE <class T> และ c7 ไม่ใช่ int แต่เป็น T :: size_type ฉันมีค่าเริ่มต้นเป็น "-1" ในส่วนหัว แต่เสียงดังขึ้นบอกว่าค่าที่ไม่ได้กำหนดและฉันไม่รู้จะเขียนคำจำกัดความอย่างไร
Fabian

@ ฟาเบียนฉันกำลังเดินทางและใช้โทรศัพท์และยุ่งนิดหน่อย ... แต่ฉันคิดว่าความคิดเห็นของคุณดูเหมือนจะดีที่สุดที่จะเขียนเป็นคำถามใหม่ เขียนMCVEรวมถึงข้อผิดพลาดที่คุณได้รับอาจจะโยนสิ่งที่ gcc พูด ฉันพนันได้เลยว่าผู้คนจะบอกคุณได้อย่างรวดเร็วว่าอะไรคืออะไร
HostileFork บอกว่าอย่าไว้ใจ SE

@HostileFork: เมื่อเขียน MCVE บางครั้งคุณก็คิดหาวิธีแก้ปัญหาด้วยตัวเอง สำหรับกรณีของฉันคำตอบคือtemplate<class K, class V, class C> const typename AE<K,V,C>::KeyContainer::size_type AE<K,V,C>::c7;โดยที่ KeyContainer เป็น typedef ของ std :: vector <K> ต้องแสดงรายการพารามิเตอร์เทมเพลตทั้งหมดและเขียนชื่อประเภทเนื่องจากเป็นประเภทที่ขึ้นอยู่กับ อาจมีคนพบว่าความคิดเห็นนี้มีประโยชน์ อย่างไรก็ตามตอนนี้ฉันสงสัยว่าจะส่งออกสิ่งนี้ใน DLL ได้อย่างไรเนื่องจากคลาสเทมเพลตอยู่ในส่วนหัวแน่นอน ต้องส่งออก c7 ไหม ???
Fabian

24

อีกวิธีหนึ่งในการทำเช่นนี้สำหรับประเภทจำนวนเต็มคือการกำหนดค่าคงที่เป็น enums ในคลาส:

class test
{
public:
    enum { N = 10 };
};

2
และนี่อาจจะแก้ปัญหาได้ เมื่อ N ถูกใช้เป็นพารามิเตอร์สำหรับ min () จะทำให้เกิดการสร้างชั่วคราวแทนที่จะพยายามอ้างถึงตัวแปรที่มีอยู่แล้ว
Edward Strange

สิ่งนี้มีข้อได้เปรียบที่สามารถทำให้เป็นส่วนตัวได้
Agostino

11

ไม่ใช่แค่ int แต่คุณไม่สามารถกำหนดค่าในการประกาศคลาสได้ ถ้าคุณมี:

class classname
{
    public:
       static int const N;
}

ในไฟล์. h คุณต้องมี:

int const classname::N = 10;

ในไฟล์. cpp


2
ฉันทราบว่าคุณสามารถประกาศตัวแปรประเภทใดก็ได้ในการประกาศคลาส ฉันบอกว่าฉันคิดว่าค่าคงที่จำนวนเต็มคงสามารถกำหนดได้ในการประกาศคลาส กรณีนี้ไม่ใช่หรือ ถ้าไม่เป็นเช่นนั้นเหตุใดคอมไพลเลอร์จึงไม่ให้ข้อผิดพลาดที่บรรทัดที่ฉันพยายามกำหนดภายในคลาส ยิ่งไปกว่านั้นเหตุใด std :: cout line จึงไม่ทำให้เกิดข้อผิดพลาดของ linker แต่ std :: min line ทำ?
HighCommander4

ไม่ไม่สามารถกำหนดสมาชิกแบบคงที่ในการประกาศคลาสได้เนื่องจากการเริ่มต้นปล่อยรหัส ซึ่งแตกต่างจากฟังก์ชันอินไลน์ที่ปล่อยโค้ดด้วยเช่นกันคำจำกัดความแบบคงที่ไม่ซ้ำกันทั่วโลก
Amardeep AC9MF

@ HighCommander4: คุณสามารถจัดหา initializer สำหรับstatic constสมาชิกอินทิกรัลในนิยามคลาสได้ แต่นั่นก็ยังไม่ได้กำหนดสมาชิกนั้น ดูคำตอบของ Noah Roberts สำหรับรายละเอียด
AnT

9

นี่เป็นอีกวิธีหนึ่งในการแก้ไขปัญหา:

std::min(9, int(test::N));

(ฉันคิดว่าคำตอบของ Crazy Eddie อธิบายได้อย่างถูกต้องว่าเหตุใดจึงมีปัญหา)


5
หรือแม้กระทั่งstd::min(9, +test::N);
Tomilov Anatoliy

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

6

ตั้งแต่ C ++ 11 คุณสามารถใช้:

static constexpr int N = 10;

ในทางทฤษฎีนี้ยังคงต้องการให้คุณกำหนดค่าคงที่ในไฟล์. cpp แต่ตราบใดที่คุณไม่ใช้ที่อยู่ของNมันก็ไม่น่าเป็นไปได้มากที่การใช้งานคอมไพเลอร์ใด ๆ จะทำให้เกิดข้อผิดพลาด;)


แล้วถ้าคุณต้องส่งค่าเป็นอาร์กิวเมนต์ประเภท 'const int &' เหมือนในตัวอย่างล่ะ? :-)
Wormer

ใช้งานได้ดี คุณไม่ได้สร้างอินสแตนซ์ N ด้วยวิธีนั้นเพียงแค่ส่งการอ้างอิง const ไปยังชั่วคราว wandbox.org/permlink/JWeyXwrVRvsn9cBj
Carlo Wood

C ++ 17 อาจไม่ใช่ C ++ 14 และแม้จะไม่ใช่ C ++ 17 ในเวอร์ชันก่อนหน้าของ gcc 6.3.0 และต่ำกว่าก็ไม่ใช่สิ่งมาตรฐาน แต่ขอบคุณที่พูดถึงเรื่องนี้
Wormer

ใช่คุณพูดถูก ฉันไม่ได้ลอง c ++ 14 บน Wandbox อ้อนั่นคือส่วนที่ผมพูดว่า "นี่ในทางทฤษฎียังต้องให้คุณกำหนดค่าคงที่" คุณคิดถูกแล้วที่ไม่ใช่ 'มาตรฐาน'
Carlo Wood

3

C ++ อนุญาตให้กำหนดสมาชิก const แบบคงที่ภายในคลาส

ไม่ 3.1 §2พูดว่า:

การประกาศเป็นคำจำกัดความเว้นแต่จะประกาศฟังก์ชันโดยไม่ระบุเนื้อความของฟังก์ชัน (8.4) จะมีตัวระบุภายนอก (7.1.1) หรือข้อกำหนดการเชื่อมโยง (7.5) และไม่มีทั้งตัวเริ่มต้นหรือตัวฟังก์ชันใครก็จะประกาศข้อมูลคงที่ สมาชิกในนิยามคลาส (9.4) เป็นการประกาศชื่อคลาส (9.1) เป็นการประกาศแบบทึบแสง (7.2) หรือเป็นการประกาศแบบ typedef (7.1.3) เป็นการประกาศโดยใช้ (7.3.2) 3) หรือคำสั่งโดยใช้ (7.3.4)

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