สตริงคงที่คงที่ (สมาชิกคลาส)


445

ฉันต้องการให้ค่าคงที่ส่วนตัวสำหรับคลาส (ในกรณีนี้คือโรงงานที่มีรูปร่าง)

ฉันอยากได้ของบางอย่าง

class A {
   private:
      static const string RECTANGLE = "rectangle";
}

น่าเสียดายที่ฉันได้รับข้อผิดพลาดทุกประเภทจากคอมไพเลอร์ C ++ (g ++) เช่น:

ISO C ++ ห้ามการเริ่มต้นของสมาชิก 'RECTANGLE'

การเตรียมใช้งานในคลาสที่ไม่ถูกต้องของข้อมูลสมาชิกแบบคงที่ของประเภทไม่ครบถ้วน 'std :: string'

ข้อผิดพลาด: การทำให้ 'RECTANGLE' เป็นแบบคงที่

สิ่งนี้บอกฉันว่าการออกแบบสมาชิกประเภทนี้ไม่เป็นไปตามมาตรฐาน คุณมีค่าคงที่ตัวอักษรส่วนตัว (หรืออาจเป็นแบบสาธารณะ) ได้อย่างไรโดยไม่ต้องใช้ #define directive (ฉันต้องการหลีกเลี่ยงความน่าเกลียดของข้อมูลข้อมูล!)

ความช่วยเหลือใด ๆ ที่ชื่นชม


15
ขอบคุณสำหรับคำตอบที่ยอดเยี่ยมของคุณ! อายุยืนดังนั้น!
ปอนด์

ใครช่วยบอกฉันทีว่า 'อินทิกรัล' คืออะไร ขอบคุณมาก.
ปอนด์

1
ประเภทหนึ่งหมายถึงประเภทที่เป็นตัวแทนของจำนวนเต็ม ดูpublib.boulder.ibm.com/infocenter/comphelp/v8v101/…
bleater

สตริงคงที่แบบส่วนตัวในโรงงานของคุณไม่ใช่วิธีแก้ปัญหาที่ดี - ให้พิจารณาว่าไคลเอนต์โรงงานของคุณจะต้องรู้ว่ารูปร่างใดได้รับการสนับสนุนดังนั้นแทนที่จะเก็บไว้ในรูปแบบส่วนตัวคงที่ให้ใส่ไว้ในเนมสเปซ "
LukeCodeBaker

หากคลาสของคุณเป็นคลาสเทมเพลตให้ดูstackoverflow.com/q/3229883/52074
Trevor Boyd Smith

คำตอบ:


471

คุณต้องกำหนดสมาชิกแบบสแตติกของคุณภายนอกคำจำกัดความของคลาสและระบุค่าเริ่มต้นที่นั่น

เป็นครั้งแรก

// In a header file (if it is in a header file in your case)
class A {   
private:      
  static const string RECTANGLE;
};

แล้ว

// In one of the implementation files
const string A::RECTANGLE = "rectangle";

เดิมไวยากรณ์ที่คุณพยายามจะใช้ (initializer ภายในคำจำกัดความของคลาส) ได้รับอนุญาตให้ใช้กับ integral และ enum เท่านั้น


เริ่มต้นจาก C ++ 17 คุณมีตัวเลือกอื่นซึ่งคล้ายกับการประกาศเริ่มต้นของคุณ: ตัวแปรอินไลน์

// In a header file (if it is in a header file in your case)
class A {   
private:      
  inline static const string RECTANGLE = "rectangle";
};

ไม่จำเป็นต้องมีคำนิยามเพิ่มเติม

หรือแทนคุณconstสามารถประกาศconstexprในตัวแปรนี้ อย่างชัดเจนinlineจะไม่ได้มีความจำเป็นเนื่องจากหมายถึงconstexprinline


8
นอกจากนี้หากไม่มีข้อกำหนดสำหรับการใช้สตริง STL คุณก็อาจกำหนด const char * (ค่าใช้จ่ายน้อยลง)
KSchmidt

50
ฉันไม่แน่ใจว่าค่าใช้จ่ายจะน้อยกว่าเสมอ - ขึ้นอยู่กับการใช้งาน หากสมาชิกนี้มีความหมายที่จะถูกส่งผ่านเป็นอาร์กิวเมนต์ไปยังฟังก์ชั่นที่ใช้สตริง const & จะมีการสร้างชั่วคราวสำหรับการโทรแต่ละครั้งกับการสร้างวัตถุสตริงหนึ่งในระหว่างการเริ่มต้น ค่าใช้จ่าย IMHO เหนือศีรษะสำหรับการสร้างวัตถุสตริงแบบคงที่นั้นไม่สามารถละเลยได้
Tadeusz Kopec

23
ฉันควรใช้ std :: string ตลอดเวลาด้วย ค่าใช้จ่ายเล็กน้อย แต่คุณมีตัวเลือกมากขึ้นและมีโอกาสน้อยที่จะเขียนเรื่องโง่ ๆ อย่าง "magic" == A :: RECTANGLE เพื่อเปรียบเทียบที่อยู่ของพวกเขาเท่านั้น ...
Matthieu M.

9
char const*มีคุณงามความดีที่ว่ามันจะเริ่มต้นก่อนการเริ่มต้นที่จะทำแบบไดนามิก ดังนั้นในคอนสตรัคเตอร์ของวัตถุใด ๆ คุณสามารถไว้วางใจRECTANGLEได้ว่าได้รับการเตรียมใช้งานก่อนหน้านี้แล้ว
Johannes Schaub - litb

8
@cirosantilli: เพราะจากจุดเริ่มต้นครั้งใน C ++ initializers เป็นส่วนของคำจำกัดความไม่ได้ประกาศ และการประกาศข้อมูลสมาชิกภายในคลาสนั้นก็คือ: การประกาศ (ในทางกลับกันมีข้อยกเว้นสำหรับสมาชิก const integral และ enum และใน C ++ 11 - สำหรับสมาชิก const ประเภทที่แท้จริง )
An

153

ใน C ++ 11 คุณสามารถทำได้ตอนนี้:

class A {
 private:
  static constexpr const char* STRING = "some useful string constant";
};

30
น่าเสียดายที่โซลูชันนี้ใช้ไม่ได้กับ std :: string
HelloWorld

2
โปรดทราบว่า 1. สิ่งนี้ใช้งานได้กับตัวอักษรเท่านั้นและ 2 ตัวนี้ไม่เป็นไปตามมาตรฐานแม้ว่า Gnu / GCC จะต้องเสียค่าปรับ แต่คอมไพเลอร์อื่น ๆ ก็จะโยนข้อผิดพลาด คำจำกัดความต้องอยู่ในร่างกาย
ManuelSchneid3r

2
@ ManuelSchneid3r นี่เป็นวิธีที่ "ไม่ได้มาตรฐานสอดคล้อง"? ดูเหมือนว่าฉันจะเริ่มต้นวงเล็บปีกกามาตรฐาน C ++ 11 หรือเท่ากับ
underscore_d

3
@ rvighne ไม่ถูกต้อง constexprหมายถึงconstvar ไม่ใช่สำหรับจุดที่พิมพ์ คือstatic constexpr const char* constเป็นเช่นเดียวแต่ไม่เหมือนกันstatic constexpr const char* static constexpr char*
midenok

2
@ abyss.7 - ขอบคุณสำหรับคำตอบของคุณและฉันมีอีกโปรด: ทำไมมันต้องคงที่?
Guy Avraham

34

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

ฉันต้องการพูดถึงว่าฉันไม่เห็นประโยชน์ของการใช้ std :: string over const char [] สำหรับค่าคงที่ std :: string เป็นสิ่งที่ดีและทั้งหมด แต่มันต้องมีการเริ่มต้นแบบไดนามิก ดังนั้นถ้าคุณเขียนอะไรบางอย่างเช่น

const std::string foo = "hello";

ที่ขอบเขตเนมสเปซคอนสตรัคเตอร์ของ foo จะถูกเรียกใช้ก่อนที่จะเริ่มการทำงานหลักและตัวสร้างนี้จะสร้างสำเนาของค่าคงที่ "hello" ในหน่วยความจำฮีป นอกจากว่าคุณต้องการ RECTANGLE เป็น std :: string คุณก็สามารถเขียนได้เช่นกัน

// class definition with incomplete static member could be in a header file
class A {
    static const char RECTANGLE[];
};

// this needs to be placed in a single translation unit only
const char A::RECTANGLE[] = "rectangle";

มี! ไม่มีการจัดสรรฮีปไม่มีการคัดลอกไม่มีการกำหนดค่าเริ่มต้นแบบไดนามิก

ไชโย


1
นี่คือคำตอบก่อน C ++ 11 ใช้ C ++ มาตรฐานและใช้ std :: string_view

1
C ++ 11 ไม่มี std :: string_view
Lukas Salich

17

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

class foo
{
public:
    static const std::string& RECTANGLE(void)
    {
        static const std::string str = "rectangle";

        return str;
    }
};

แม้ว่าฉันจะสงสัยว่าแนะนำ


มันดูเท่ห์ :) - ฉันเดาว่าคุณมีพื้นหลังเป็นภาษาอื่นที่ไม่ใช่ c ++ ใช่ไหม
ปอนด์

5
ฉันไม่อยากจะแนะนำ ฉันทำสิ่งนี้บ่อยครั้ง มันใช้งานได้ดีและฉันคิดว่ามันชัดเจนกว่าการใส่สตริงลงในไฟล์การนำไปใช้งาน ข้อมูลจริงของ std :: string ยังคงอยู่บน heap แม้ว่า ฉันจะคืนค่า const char * ซึ่งในกรณีนี้คุณไม่จำเป็นต้องประกาศตัวแปรแบบคงที่ดังนั้นการประกาศจะใช้พื้นที่น้อยลง (โค้ดฉลาด) แค่เรื่องของการลิ้มรส
Zoomulator

15

ใน C ++ 17 คุณสามารถใช้ตัวแปรอินไลน์ได้ :

class A {
 private:
  static inline const std::string my_string = "some useful string constant";
};

โปรดทราบว่าสิ่งนี้แตกต่างจากคำตอบของ abyss.7 : อันนี้กำหนดstd::stringวัตถุจริงไม่ใช่const char*


คุณไม่คิดว่าการใช้inlineจะสร้างข้อมูลซ้ำซ้อนมากมายใช่ไหม
shuva

1
@shuva ไม่มีที่ตัวแปรจะไม่ซ้ำกัน
zett42

8

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

นี่คือข้อ จำกัด ดังนั้นในกรณีนี้คุณต้องกำหนดตัวแปรนอกคลาส อ้างถึงคำตอบจาก @AndreyT


7

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

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

class A
{
private:
  static struct _Shapes {
     const std::string RECTANGLE {"rectangle"};
     const std::string CIRCLE {"circle"};
  } shape;
};

แทนที่จะเริ่มสมาชิกแต่ละรายโครงสร้างแบบคงที่ทั้งหมดจะเริ่มต้นใน. cpp:

A::_Shapes A::shape;

ค่าที่เข้าถึงได้ด้วย

A::shape.RECTANGLE;

หรือ - เนื่องจากสมาชิกเป็นแบบส่วนตัวและมีความตั้งใจที่จะใช้จาก A - with เท่านั้น

shape.RECTANGLE;

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

// file.h
class File {
public:
  static struct _Extensions {
    const std::string h{ ".h" };
    const std::string hpp{ ".hpp" };
    const std::string c{ ".c" };
    const std::string cpp{ ".cpp" };
  } extension;
};

// file.cpp
File::_Extensions File::extension;

// module.cpp
static std::set<std::string> headers{ File::extension.h, File::extension.hpp };

ในกรณีนี้ตัวแปรคงที่ ส่วนหัวของจะมี {""} หรือ {".h", ".hpp"} ขึ้นอยู่กับลำดับของการเริ่มต้นที่สร้างขึ้นโดย linker

ตามที่กล่าวไว้โดย @ abyss.7 คุณสามารถใช้constexprหากค่าของตัวแปรสามารถคำนวณได้ในเวลารวบรวม แต่ถ้าคุณประกาศสตริงของคุณด้วยstatic constexpr const char*และโปรแกรมของคุณใช้เป็นstd::stringอย่างอื่นจะมีค่าใช้จ่ายเพราะstd::stringวัตถุใหม่จะถูกสร้างขึ้นทุกครั้งที่คุณใช้ค่าคงที่:

class A {
public:
   static constexpr const char* STRING = "some value";
};
void foo(const std::string& bar);
int main() {
   foo(A::STRING); // a new std::string is constructed and destroyed.
}

คำตอบที่เตรียมไว้อย่างดีมาร์โค สองรายละเอียด: หนึ่งไฟล์ไม่ต้องการไฟล์ cpp สำหรับสมาชิกคลาสแบบสแตติกและโปรดใช้ std :: string_view สำหรับค่าคงที่ประเภทใด ๆ

4

มาตรฐานปัจจุบันอนุญาตให้เริ่มต้นดังกล่าวสำหรับประเภทคงที่หนึ่งคงที่เท่านั้น ดังนั้นคุณต้องทำตามที่ AndreyT อธิบาย แต่ที่จะสามารถใช้ได้ในมาตรฐานต่อไปผ่านไวยากรณ์การเริ่มต้นสมาชิกใหม่


4

เป็นไปได้เพียงทำ:

static const std::string RECTANGLE() const {
    return "rectangle";
} 

หรือ

#define RECTANGLE "rectangle"

11
การใช้ #define เมื่อค่าคงที่ที่พิมพ์สามารถใช้ผิดได้
Artur Czajka

ตัวอย่างแรกของคุณนั้นเป็นทางออกที่ดีถ้าคุณไม่มีconstexprแต่คุณไม่สามารถสร้างฟังก์ชั่นคงที่constได้
Frank Puffer

วิธีนี้ควรหลีกเลี่ยง มันจะสร้างสตริงใหม่ในทุกคำขอร้อง จะดีกว่านี้:static const std::string RECTANGLE() const { static const std::string value("rectangle"); return value; }
Oz Solomon

เหตุใดจึงใช้คอนเทนเนอร์แบบเต็มเป่าเป็นค่าส่งคืน ใช้ std :: string_vew .. เนื้อหาจะยังคงใช้ได้ในกรณีนี้ ยิ่งใช้ตัวอักษรสตริงให้ดีขึ้นเพื่อสร้างและส่งคืนมุมมองสตริง ... และสุดท้าย แต่ไม่ใช่อย่างน้อยค่าส่งคืน const ไม่มีความหมายหรือผลกระทบที่นี่ .. ใช่แล้วและมีสิ่งนี้เป็นแบบอินไลน์ไม่ใช่แบบคงที่ในบางส่วนของ ชื่อเนมสเปซ ... และโปรดกำหนดให้เป็น constexpr

4

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

//in a header  
class A{  
  static string s;   
public:   
  static string getS();  
};  
//in implementation  
string A::s;  
namespace{  
  bool init_A_s(){  
    A::s = string("foo");   
    return true;  
  }  
  bool A_s_initialized = init_A_s();  
}  
string A::getS(){      
  if (!A_s_initialized)  
    A_s_initialized = init_A_s();  
  return s;  
}  

A::getS()จำไว้ว่าให้ใช้เท่านั้น เนื่องจากเธรดใด ๆ สามารถเริ่มต้นด้วยmain()และA_s_initializedเริ่มต้นก่อนmain()คุณไม่จำเป็นต้องล็อกแม้ในสภาพแวดล้อมแบบมัลติเธรด A_s_initializedเป็น 0 โดยค่าเริ่มต้น (ก่อนการเริ่มต้นแบบไดนามิก) ดังนั้นหากคุณใช้getS()ก่อนที่จะเริ่มต้นได้คุณจะเรียกใช้ฟังก์ชัน init ได้อย่างปลอดภัย

Btw, ในคำตอบข้างต้น: " static const std :: string RECTANGLE () const ", ฟังก์ชั่นคงที่ไม่สามารถเป็นconstเพราะพวกเขาไม่สามารถเปลี่ยนสถานะหากวัตถุใด ๆ อยู่แล้ว (ไม่มีตัวชี้นี้)


4

กรอไปข้างหน้าสู่ 2018 และ C ++ 17

  • ห้ามใช้ std :: string ใช้ std :: string_view literals
  • โปรดสังเกตเสียงร้อง 'constexpr' นอกจากนี้ยังเป็นกลไก "เวลารวบรวม"
  • ไม่มีอินไลน์ไม่ได้หมายถึงการซ้ำซ้อน
  • ไม่จำเป็นต้องใช้ไฟล์ cpp
  • static_assert 'ทำงาน' ที่รวบรวมเวลาเท่านั้น

    using namespace std::literals;
    
    namespace STANDARD {
    constexpr 
    inline 
    auto 
    compiletime_static_string_view_constant() {
    // make and return string view literal
    // will stay the same for the whole application lifetime
    // will exhibit standard and expected interface
    // will be usable at both
    // runtime and compile time
    // by value semantics implemented for you
        auto when_needed_ =  "compile time"sv;
        return when_needed_  ;
    }

    };

ข้างต้นเป็นพลเมือง C ++ ที่ถูกต้องและถูกกฎหมาย มันสามารถมีส่วนร่วมได้อย่างง่ายดายในทุก std :: อัลกอริทึม, ภาชนะ, สาธารณูปโภคและเช่น ตัวอย่างเช่น:

// test the resilience
auto return_by_val = []() {
    auto return_by_val = []() {
        auto return_by_val = []() {
            auto return_by_val = []() {
return STANDARD::compiletime_static_string_view_constant();
            };
            return return_by_val();
        };
        return return_by_val();
    };
    return return_by_val();
};

// actually a run time 
_ASSERTE(return_by_val() == "compile time");

// compile time 
static_assert(
   STANDARD::compiletime_static_string_view_constant() 
   == "compile time" 
 );

เพลิดเพลินกับ C ++ มาตรฐาน


ใช้std::string_viewสำหรับค่าคงที่เฉพาะเมื่อคุณใช้string_viewพารามิเตอร์ในทุกฟังก์ชั่นของคุณ หากฟังก์ชันใด ๆ ของคุณใช้const std::string&พารามิเตอร์สำเนาของสตริงจะถูกสร้างขึ้นเมื่อคุณส่งstring_viewค่าคงที่ผ่านพารามิเตอร์นั้น หากค่าคงที่ของคุณเป็นประเภทstd::stringการคัดลอกจะไม่ถูกสร้างขึ้นสำหรับconst std::string&พารามิเตอร์หรือสำหรับstd::string_viewพารามิเตอร์
Marko Mahnič

คำตอบที่ดี แต่อยากรู้ว่าเหตุใด string_view จึงถูกส่งคืนจากฟังก์ชัน กลอุบายประเภทนี้มีประโยชน์ก่อนที่inlineตัวแปรจะมาถึงใน C ++ 17 ที่มีความหมาย ODR แต่ string_view ก็คือ C ++ 17 เช่นกันดังนั้นเพียงแค่constexpr auto some_str = "compile time"sv;ทำงาน (และที่จริงแล้วมันไม่ใช่ตัวแปรมันก็เป็นconstexprเช่นinlineนั้นโดยปริยายถ้าคุณมีตัวแปร - เช่นไม่มีconstexpr- inline auto some_str = "compile time"sv;จะทำมันแม้ว่าแน่นอนว่าขอบเขตของ namespace ตัวแปรซึ่งโดยหลักแล้วคือตัวแปรทั่วโลกแทบจะไม่เป็นความคิดที่ดีเลย)
การสูญเสียความคิด
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.