ตัวสร้างแบบคงที่ใน C ++? ฉันต้องเริ่มต้นวัตถุคงที่ส่วนตัว


176

ฉันต้องการคลาสที่มีสมาชิกข้อมูลสแตติกส่วนตัว (เวกเตอร์ที่มีอักขระ az ทั้งหมด) ใน java หรือ C # ฉันสามารถสร้าง "static constructor" ที่จะทำงานก่อนที่ฉันจะสร้างอินสแตนซ์ของคลาสและตั้งค่าสมาชิกข้อมูลคงที่ของคลาส มันถูกเรียกใช้เพียงครั้งเดียว (เนื่องจากตัวแปรถูกอ่านอย่างเดียวและจำเป็นต้องตั้งค่าเพียงครั้งเดียว) และเนื่องจากเป็นฟังก์ชันของคลาสจึงสามารถเข้าถึงสมาชิกส่วนตัวได้ ฉันสามารถเพิ่มโค้ดใน Constructor ที่ตรวจสอบเพื่อดูว่าเวกเตอร์เริ่มต้นได้หรือไม่และเริ่มต้นถ้าไม่ใช่ แต่แนะนำการตรวจสอบที่จำเป็นมากมายและดูเหมือนจะไม่เป็นทางออกที่ดีที่สุดสำหรับปัญหา

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

เป็นไปได้หรือไม่ที่จะมีสมาชิกข้อมูลสแตติกส่วนตัวในชั้นเรียนหากฉันไม่ต้องการเริ่มต้นพวกเขาใน Constructor อินสแตนซ์?



1
@CiroSantilli 新疆改造中心六四事件法轮功คำถามนี้มุ่งเน้นไปที่การเรียกใช้รหัสเพื่อเริ่มต้นวัตถุคงที่ส่วนตัวไม่ได้ตั้งค่าคงที่ของประเภทดั้งเดิมดั้งเดิมคงที่ การแก้ปัญหาต่างกัน
Gordon Gustafson

อ่าฉันคิดว่าคุณพูดถูก
Ciro Santilli 郝海东冠状病六四事件法轮功

คำตอบ:


180

ในการรับค่าคงที่ของตัวสร้างแบบคงที่คุณจะต้องเขียนคลาสสามัญแยกต่างหากเพื่อเก็บข้อมูลแบบคงที่จากนั้นสร้างอินสแตนซ์แบบคงที่ของคลาสสามัญนั้น

class StaticStuff
{
     std::vector<char> letters_;

public:
     StaticStuff()
     {
         for (char c = 'a'; c <= 'z'; c++)
             letters_.push_back(c);
     }

     // provide some way to get at letters_
};

class Elsewhere
{
    static StaticStuff staticStuff; // constructor runs once, single instance

};

12
ขอบคุณ! แม้ว่ามันจะน่ารำคาญมากที่ต้องทำทุกอย่าง หนึ่งใน "ความผิดพลาด" C # และ Java เรียนรู้จาก
Gordon Gustafson

109
ใช่. ฉันมักจะชี้ให้ผู้คนเห็นว่าหาก C ++ ไม่ได้ทำ "ความผิดพลาด" เหล่านั้นทั้งหมดภาษาอื่น ๆ ก็จะต้องทำให้พวกเขา C ++ ครอบคลุมพื้นที่มากแม้กระทั่งการทำผิดพลาดได้ดีมากสำหรับภาษาที่ตามมา
quark

11
ความแตกต่างเพียงเล็กน้อยเดียวขณะที่ตัวสร้างเข้ามาไม่มีใครรับประกันเมื่อตัวสร้างสำหรับวัตถุคงที่ดำเนินการ วิธีที่ปลอดภัยกว่าที่รู้จักกันดีคือคลาสที่อื่น {StaticStuff & get_staticStuff () {staticStartStuffStuffStuffStuffStuffStuff // คอนสตรัคทำงานครั้งเดียวเมื่อมีคนต้องการมันกลับ staticStuff; }}; ฉันสงสัยว่าการก่อสร้างแบบคงที่ใน C # และ Java สามารถให้การรับประกันเช่นเดียวกับโค้ดข้างต้น ...
Oleg Zhylin

13
@Oleg: ใช่พวกเขาทำ มาตรฐานการรับประกันที่ตัวสร้างสำหรับตัวแปรท้องถิ่นทั้งหมดจะถูกดำเนินการก่อนที่จะมีการป้อนหลัก นอกจากนี้ยังรับประกันว่าภายในหน่วยการรวบรวมคำสั่งของการก่อสร้างมีการกำหนดไว้เป็นอย่างดีและคำสั่งเดียวกับการประกาศภายในหน่วยการรวบรวม น่าเสียดายที่พวกเขาไม่ได้กำหนดลำดับในการรวบรวมหลาย ๆ หน่วย
34290 Martin Martin York

13
นี่เป็นกรณีที่friendเหมาะสมอย่างยิ่งเพื่อให้ชั้นเรียนElsewhereสามารถเข้าถึงStaticStuffภายในได้อย่างง่ายดาย(โดยไม่ทำลายความห่อหุ้มด้วยวิธีที่อันตราย
Konrad Rudolph

81

คุณสามารถมี

class MyClass
{
    public:
        static vector<char> a;

        static class _init
        {
          public:
            _init() { for(char i='a'; i<='z'; i++) a.push_back(i); }
        } _initializer;
};

อย่าลืม (ใน. cpp) สิ่งนี้:

vector<char> MyClass::a;
MyClass::_init MyClass::_initializer;

โปรแกรมจะยังคงเชื่อมโยงโดยไม่มีบรรทัดที่สอง แต่ initializer จะไม่ถูกดำเนินการ


+1 (ไม่ลอง) แต่: เมื่อเรียก ctor _init._init () เมื่อใด ก่อนหรือหลัง ctor ของ MyClass เมื่อฉันมีวัตถุ MyClass คงที่? ผมคิดว่าคุณไม่สามารถบอกได้ ...
ur

2
สวัสดีฉันจะหาข้อมูลเพิ่มเติมเกี่ยวกับเวทย์มนตร์ "initializer" นี้ได้ที่ไหน
Karel Bílek

ไม่ควรที่จะเป็นMyClass::a.push_back(i)แทนa.push_back(i)?
Neel Basu

4
@ur .: _initializerเป็น subobject MyClassของ Subobjects ถูกเตรียมใช้งานในลำดับนี้: subobjects คลาสฐานเสมือนในลำดับความลึกแรกจากซ้ายไปขวา (แต่เริ่มต้นแต่ละ subobject ที่แตกต่างกันเพียงครั้งเดียว) subobjects คลาสฐานธรรมดาจากนั้นในลำดับความลึกแรกจากซ้ายไปขวา แล้ว subobjects ของสมาชิกตามลำดับของการประกาศ ดังนั้นจึงปลอดภัยที่จะใช้กลยุทธ์ของ EFraim หากรหัส_initialiserนั้นหมายถึงสมาชิกที่ประกาศไว้ก่อนหน้าเท่านั้น
j_random_hacker

2
คำตอบนี้ดีกว่าคำตอบที่ยอมรับเพราะผู้เขียนกล่าวถึงการเริ่มต้นที่ขาดไม่ได้ในคลิปโค้ดที่สอง
เจฟฟ์ที.

33

โซลูชัน C ++ 11

ตั้งแต่ C ++ 11 คุณสามารถใช้แลมบ์ดานิพจน์เพื่อเริ่มต้นสมาชิกคลาสแบบคงที่ได้ นี้ยังทำงานถ้าคุณต้องการที่จะกำหนดคำสั่งของการก่อสร้างระหว่างสมาชิกแบบคงที่ต่าง ๆ constหรือหากคุณมีสมาชิกแบบคงที่

ไฟล์ส่วนหัว:

class MyClass {
    static const vector<char> letters;
    static const size_t letterCount;
};

ไฟล์ต้นฉบับ:

// Initialize MyClass::letters by using a lambda expression.
const vector<char> MyClass::letters = [] {
    vector<char> letters;
    for (char c = 'a'; c <= 'z'; c++)
        letters.push_back(c);
    return letters;
}();

// The initialization order of static members is defined by the order of
// definition within the source file, so we can access MyClass::letters here.
const size_t MyClass::letterCount = letters.size();

ทางออกที่น่าสนใจ ในกรณีนี้ถ้าฉันโยนข้อยกเว้นที่สามารถจับได้หรือไม่
rafi Wiener

5
รหัสการเริ่มต้นโปรแกรมแบบสแตติกต้องไม่ทิ้งข้อยกเว้นใด ๆ มิฉะนั้นโปรแกรมจะหยุดทำงาน คุณต้องล้อมตรรกะการเริ่มต้นไว้ในtry catchบล็อกหากอาจมีข้อยกเว้นเกิดขึ้น
emkey08

19

ในไฟล์. h:

class MyClass {
private:
    static int myValue;
};

ในไฟล์. cpp:

#include "myclass.h"

int MyClass::myValue = 0;

5
วิธีนี้ใช้ได้ผลดีสำหรับสมาชิกแบบสแตติกแต่ละคน (โดยไม่คำนึงถึงประเภท) ข้อบกพร่องในการเปรียบเทียบกับตัวสร้างแบบคงที่คือคุณไม่สามารถกำหนดลำดับระหว่างสมาชิกแบบคงที่ต่างๆ หากคุณต้องทำอย่างนั้นดูคำตอบของ Earwicker
quark

ฉันทำอย่างนั้น แต่ก็ยังไม่ได้รวบรวม และมันบอกว่านี่เป็นพื้นที่ที่มีปัญหา (ในตัวสร้างไม่ใช่ส่วนหัว)
Flotolk

14

นี่เป็นวิธีการอื่นที่คล้ายกับ Daniel Earwicker's โดยใช้คำแนะนำเพื่อนของ Konrad Rudolph ที่นี่เราใช้คลาสยูทิลิตี้เพื่อนภายในส่วนตัวเพื่อเริ่มต้นสมาชิกแบบคงที่ของคลาสหลักของคุณ ตัวอย่างเช่น:

ไฟล์ส่วนหัว:

class ToBeInitialized
{
    // Inner friend utility class to initialize whatever you need

    class Initializer
    {
    public:
        Initializer();
    };

    friend class Initializer;

    // Static member variables of ToBeInitialized class

    static const int numberOfFloats;
    static float *theFloats;

    // Static instance of Initializer
    //   When this is created, its constructor initializes
    //   the ToBeInitialized class' static variables

    static Initializer initializer;
};

ไฟล์การใช้งาน:

// Normal static scalar initializer
const int ToBeInitialized::numberOfFloats = 17;

// Constructor of Initializer class.
//    Here is where you can initialize any static members
//    of the enclosing ToBeInitialized class since this inner
//    class is a friend of it.

ToBeInitialized::Initializer::Initializer()
{
    ToBeInitialized::theFloats =
        (float *)malloc(ToBeInitialized::numberOfFloats * sizeof(float));

    for (int i = 0; i < ToBeInitialized::numberOfFloats; ++i)
        ToBeInitialized::theFloats[i] = calculateSomeFancyValue(i);
}

วิธีการนี้มีข้อได้เปรียบในการซ่อนคลาส Initializer จากโลกภายนอกอย่างสมบูรณ์ทำให้ทุกอย่างที่อยู่ในคลาสนั้นถูกเตรียมใช้งาน


+1 สำหรับยกตัวอย่างที่ทำให้การนำไปใช้งานเป็นไฟล์ของตัวเอง
Andrew Larsson

1
นอกจากนี้คุณต้องตรวจสอบให้แน่ใจว่าToBeInitialized::Initializer::Initializer()ได้รับการเรียกดังนั้นคุณต้องเพิ่มToBeInitialized::Initializer ToBeInitialized::initializer;ไปยังไฟล์การใช้งาน ฉันเอาบางสิ่งจากความคิดของคุณและจากความคิดของ EFraim และมันทำงานได้อย่างที่ฉันต้องการและดูสะอาดตา ขอบคุณชาย
Andrew Larsson

11

Test::StaticTest() ถูกเรียกว่าหนึ่งครั้งในช่วงเริ่มต้นคงที่ทั่วโลก

ผู้เรียกต้องเพิ่มหนึ่งบรรทัดในฟังก์ชันที่เป็นตัวสร้างสแตติก

static_constructor<&Test::StaticTest>::c;กองกำลังเริ่มต้นของcในระหว่างการเริ่มต้นคงที่ทั่วโลก

template<void(*ctor)()>
struct static_constructor
{
    struct constructor { constructor() { ctor(); } };
    static constructor c;
};

template<void(*ctor)()>
typename static_constructor<ctor>::constructor static_constructor<ctor>::c;

/////////////////////////////

struct Test
{
    static int number;

    static void StaticTest()
    {
        static_constructor<&Test::StaticTest>::c;

        number = 123;
        cout << "static ctor" << endl;
    }
};

int Test::number;

int main(int argc, char *argv[])
{
    cout << Test::number << endl;
    return 0;
}

นี่เป็นทางออกที่ยอดเยี่ยม ฉันชอบคำตอบของดักลาสแมนเดลเช่นกัน แต่นี่ก็กระชับกว่านี้
FlintZA

มันช่างยอดเยี่ยมจริงๆ!
nh_

9

ไม่จำเป็นต้องมีinit()ฟังก์ชั่นstd::vectorสามารถสร้างได้จากช่วง:

// h file:
class MyClass {
    static std::vector<char> alphabet;
// ...
};

// cpp file:
#include <boost/range.hpp>
static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
std::vector<char> MyClass::alphabet( boost::begin( ::alphabet ), boost::end( ::alphabet ) );

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

การอัปเดต C ++ 11

ตั้งแต่ C ++ 11 คุณสามารถทำสิ่งนี้แทน:

// cpp file:
std::vector<char> MyClass::alphabet = { 'a', 'b', 'c', ..., 'z' };

มันเทียบเท่ากับโซลูชั่น C ++ 98 ในคำตอบดั้งเดิม แต่คุณไม่สามารถใช้สตริงตัวอักษรทางขวามือได้ดังนั้นมันจึงไม่ได้เหนือกว่าอย่างสิ้นเชิง แต่ถ้าคุณมีเวกเตอร์ของประเภทอื่น ๆ กว่าchar, wchar_t, char16_tหรือchar32_t(อาร์เรย์ซึ่งสามารถเขียนเป็นตัวอักษรของสตริง) รุ่น C ++ 11 อย่างเคร่งครัดจะลบรหัสสำเร็จรูปโดยไม่ต้องแนะนำไวยากรณ์อื่น ๆ เมื่อเทียบกับซี ++ 98 รุ่น


ฉันชอบมัน. แม้ว่าเพียง แต่เราสามารถทำได้ในหนึ่งบรรทัดโดยไม่มีตัวอักษรที่ไร้ประโยชน์ในขณะนี้
มาร์ตินนิวยอร์ก

ในการทำให้เกิดปัญหากับไลบรารีมันสำคัญไหมถ้าคลาสสแตติกเป็นส่วนตัวหรือสาธารณะ? นอกจากนี้มันจะสำคัญไหมหากไลบรารี่เป็น static (.a) หรือ dynamic (.so)?
Zachary Kraus

@ZacharyKraus: คลาสสาธารณะ / ส่วนตัวคืออะไร? และไม่ในขณะที่ปัญหาแตกต่างกัน แต่ทับซ้อนกันไม่สำคัญว่าไลบรารีจะถูกเชื่อมโยงแบบสแตติกหรือแบบไดนามิก
Marc Mutz - mmutz

@ MarcMutz-mmutz ขออภัยที่ใช้คลาสสาธารณะ / ส่วนตัวซึ่งไม่ถูกต้อง C ++ คำศัพท์ สิ่งที่ฉันหมายถึงคือโซลูชันของ EFraim ด้านบน ในรุ่นของฉันฉันทำให้สมาชิกคลาสแบบสแตติกเป็นส่วนตัว ฉันพยายามที่จะเข้าใจว่าการมีสมาชิกของคลาสแบบสแตติกในที่สาธารณะหรือส่วนตัวสร้างความแตกต่างในการพัฒนาห้องสมุดและการใช้งาน ไส้ของฉันบอกฉันว่ามันไม่ควรมีผลกระทบกับห้องสมุดเพราะผู้ใช้จะไม่สามารถเข้าถึงสมาชิกของคลาสแบบคงที่หรือวัตถุที่สร้าง แต่ฉันชอบที่จะได้รับภูมิปัญญาของปราชญ์ในหัวข้อนี้
Zachary Kraus

@ ZacharyKraus: ปัญหาหลักของสถิตที่จำเป็นต้องมีการกำหนดค่าเริ่มต้นแบบไดนามิก ([basic.start.init] / 2) ก็คือพวกมันรันโค้ด ในไลบรารีอาจเป็นไปได้ว่ารหัสไลบรารีได้ถูกยกเลิกการโหลดแล้วเมื่อมีการเรียกใช้ destructors หากคุณต้องการฟังมากขึ้นฉันแนะนำให้โพสต์คำถามเกี่ยวกับมัน
Marc Mutz - mmutz

6

แนวคิดของการสร้างแบบคงที่ได้รับการแนะนำใน Java หลังจากที่พวกเขาเรียนรู้จากปัญหาใน C ++ ดังนั้นเราจึงไม่เทียบเท่ากันโดยตรง

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

//header

class A
{
    // Make sure this is private so that nobody can missues the fact that
    // you are overriding std::vector. Just doing it here as a quicky example
    // don't take it as a recomendation for deriving from vector.
    class MyInitedVar: public std::vector<char>
    {
        public:
        MyInitedVar()
        {
           // Pre-Initialize the vector.
           for(char c = 'a';c <= 'z';++c)
           {
               push_back(c);
           }
        }
    };
    static int          count;
    static MyInitedVar  var1;

};


//source
int            A::count = 0;
A::MyInitedVar A::var1;

4

เมื่อพยายามรวบรวมและใช้คลาสElsewhere(จากคำตอบของ Earwicker ) ฉันได้รับ:

error LNK2001: unresolved external symbol "private: static class StaticStuff Elsewhere::staticStuff" (?staticStuff@Elsewhere@@0VStaticStuff@@A)

ดูเหมือนว่าเป็นไปไม่ได้ที่จะเริ่มต้นแอตทริบิวต์คงที่ของประเภทที่ไม่ใช่จำนวนเต็มโดยไม่ต้องใส่รหัสบางอย่างนอก class definition (CPP)

เพื่อให้การรวบรวมนั้นคุณสามารถใช้ " วิธีการคงที่กับตัวแปรท้องถิ่นคงที่ภายใน " แทน บางสิ่งเช่นนี้

class Elsewhere
{
public:
    static StaticStuff& GetStaticStuff()
    {
        static StaticStuff staticStuff; // constructor runs once, single instance
        return staticStuff;
    }
};

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

Hugo González Castro


แม้ว่าจะระมัดระวังถ้าใช้เธรด ฉันเชื่อใน GCC การสร้างสแตติกท้องถิ่นจะได้รับการปกป้องจากการดำเนินการพร้อมกัน แต่ใน Visual C ++ ไม่ใช่
Daniel Earwicker

1
จาก C ++ 11 เป็นต้นไปและใน POSIX ก็มีจะเป็นด้ายปลอดภัย
Marc Mutz - mmutz

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

! น่ากลัว มันเสร็จสมบูรณ์แล้ว
Gabe Halsmer

4

ฉันเดาทางออกง่าย ๆ สำหรับสิ่งนี้:

    //X.h
    #pragma once
    class X
    {
    public:
            X(void);
            ~X(void);
    private:
            static bool IsInit;
            static bool Init();
    };

    //X.cpp
    #include "X.h"
    #include <iostream>

    X::X(void)
    {
    }


    X::~X(void)
    {
    }

    bool X::IsInit(Init());
    bool X::Init()
    {
            std::cout<< "ddddd";
            return true;
    }

    // main.cpp
    #include "X.h"
    int main ()
    {
            return 0;
    }

นี่คือวิธีที่ฉันทำเช่นกัน
Etherealone

1

เพียงแค่แก้เคล็ดลับเดียวกัน ฉันต้องระบุคำจำกัดความของสมาชิกแบบคงที่เดียวสำหรับ Singleton แต่ทำให้สิ่งต่าง ๆ มีความซับซ้อนมากขึ้น - ฉันตัดสินใจแล้วว่าฉันไม่ต้องการโทรหา ctor ของ RandClass () เว้นแต่ฉันจะใช้มัน ... นั่นคือสาเหตุที่ฉันไม่ต้องการกำหนดค่าเริ่มต้นเดี่ยวทั่วโลกในรหัสของฉัน นอกจากนี้ฉันได้เพิ่มส่วนต่อประสานที่เรียบง่ายในกรณีของฉัน

นี่คือรหัสสุดท้าย:

ฉันย่อโค้ดและใช้ rand () ฟังก์ชั่นและ initialzer srand () เมล็ดเดียว

interface IRandClass
{
 public:
    virtual int GetRandom() = 0;
};

class RandClassSingleton
{
private:
  class RandClass : public IRandClass
  {
    public:
      RandClass()
      {
        srand(GetTickCount());
      };

     virtual int GetRandom(){return rand();};
  };

  RandClassSingleton(){};
  RandClassSingleton(const RandClassSingleton&);

  // static RandClass m_Instance;

  // If you declare m_Instance here you need to place
  // definition for this static object somewhere in your cpp code as
  // RandClassSingleton::RandClass RandClassSingleton::m_Instance;

  public:

  static RandClass& GetInstance()
  {
      // Much better to instantiate m_Instance here (inside of static function).
      // Instantiated only if this function is called.

      static RandClass m_Instance;
      return m_Instance;
  };
};

main()
{
    // Late binding. Calling RandClass ctor only now
    IRandClass *p = &RandClassSingleton::GetInstance();
    int randValue = p->GetRandom();
}
abc()
{
    IRandClass *same_p = &RandClassSingleton::GetInstance();
}

1

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

ใน.hไฟล์คุณมี:

template <typename Aux> class _MyClass
{
    public:
        static vector<char> a;
        _MyClass() {
            (void) _initializer; //Reference the static member to ensure that it is instantiated and its initializer is called.
        }
    private:
        static struct _init
        {
            _init() { for(char i='a'; i<='z'; i++) a.push_back(i); }
        } _initializer;

};
typedef _MyClass<void> MyClass;

template <typename Aux> vector<char> _MyClass<Aux>::a;
template <typename Aux> typename _MyClass<Aux>::_init _MyClass<Aux>::_initializer;

ใน.cppไฟล์คุณสามารถมี:

void foobar() {
    MyClass foo; // [1]

    for (vector<char>::iterator it = MyClass::a.begin(); it < MyClass::a.end(); ++it) {
        cout << *it;
    }
    cout << endl;
}

โปรดทราบว่าMyClass::aจะเริ่มต้นได้เฉพาะในกรณีที่เส้น [1] มีเพราะสายที่ (และต้อง instantiation ของ) _initializerคอนสตรัคซึ่งจะต้องมีการเริ่มของ


1

ต่อไปนี้เป็นวิธีการอื่นที่เวกเตอร์เป็นส่วนตัวของไฟล์ที่มีการนำไปใช้โดยใช้เนมสเปซที่ไม่ระบุชื่อ มันมีประโยชน์สำหรับสิ่งต่าง ๆ เช่นตารางการค้นหาที่เป็นส่วนตัวของการนำไปใช้งาน:

#include <iostream>
#include <vector>
using namespace std;

namespace {
  vector<int> vec;

  struct I { I() {
    vec.push_back(1);
    vec.push_back(3);
    vec.push_back(5);
  }} i;
}

int main() {

  vector<int>::const_iterator end = vec.end();
  for (vector<int>::const_iterator i = vec.begin();
       i != end; ++i) {
    cout << *i << endl;
  }

  return 0;
}

แม้ว่าคุณอาจต้องการตั้งชื่อIและiบางสิ่งบางอย่างที่คลุมเครือมากกว่าเล็กน้อยดังนั้นคุณจึงไม่ได้ตั้งใจใช้สิ่งที่ต่ำกว่าในไฟล์
Jim Hunziker

1
ตามความจริงมันยากที่จะเห็นว่าทำไมทุกคนต้องการใช้สมาชิกคงที่ส่วนตัวมากกว่า namespace anonmymous ในไฟล์การใช้งาน
Jim Hunziker

1

แน่นอนว่าไม่จำเป็นต้องยุ่งยากเหมือนคำตอบที่ยอมรับในปัจจุบัน (โดย Daniel Earwicker) คลาสนั้นไม่จำเป็น ไม่จำเป็นต้องทำสงครามภาษาในกรณีนี้

ไฟล์. hpp:

vector<char> const & letters();

ไฟล์. cpp:

vector<char> const & letters()
{
  static vector<char> v = {'a', 'b', 'c', ...};
  return v;
}


0

คุณกำหนดตัวแปรสมาชิกแบบคงที่คล้ายกับวิธีที่คุณกำหนดวิธีการสมาชิก

foo.h

class Foo
{
public:
    void bar();
private:
    static int count;
};

foo.cpp

#include "foo.h"

void Foo::bar()
{
    // method definition
}

int Foo::count = 0;

2
คำถาม CrazyJugglerDrummer ก็ไม่ได้เกี่ยวกับการคงธรรมดาชนิดข้อมูลเก่า :)
jww

0

ในการเริ่มต้นตัวแปรสแตติกคุณเพียงแค่ทำเช่นนั้นภายในไฟล์ต้นฉบับ ตัวอย่างเช่น:

//Foo.h
class Foo
{
 private:
  static int hello;
};


//Foo.cpp
int Foo::hello = 1;

คำถาม CrazyJugglerDrummer ก็ไม่ได้เกี่ยวกับการคงธรรมดาชนิดข้อมูลเก่า :)
jww

0

วิธีการเกี่ยวกับการสร้างแม่แบบเพื่อเลียนแบบพฤติกรรมของ C #

template<class T> class StaticConstructor
{
    bool m_StaticsInitialised = false;

public:
    typedef void (*StaticCallback)(void);

    StaticConstructor(StaticCallback callback)
    {
        if (m_StaticsInitialised)
            return;

        callback();

        m_StaticsInitialised = true;
    }
}

template<class T> bool StaticConstructor<T>::m_StaticsInitialised;

class Test : public StaticConstructor<Test>
{
    static std::vector<char> letters_;

    static void _Test()
    {
        for (char c = 'a'; c <= 'z'; c++)
            letters_.push_back(c);
    }

public:
    Test() : StaticConstructor<Test>(&_Test)
    {
        // non static stuff
    };
};

0

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

#include <iostream>

class MyClass 
{

    static const char * const letters(void){
        static const char * const var = "abcdefghijklmnopqrstuvwxyz";
        return var;
    }

    public:
        void show(){
            std::cout << letters() << "\n";
        }
};


int main(){
    MyClass c;
    c.show();
}

0

นี่เป็นวิธีแก้ปัญหาหรือไม่?

class Foo
{
public:
    size_t count;
    Foo()
    {
        static size_t count = 0;
        this->count = count += 1;
    }
};

0

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

class ClassStatic{
private:
    static char *str;
public:
    char* get_str() { return str; }
    void set_str(char *s) { str = s; }
    // A nested class, which used as static constructor
    static class ClassInit{
    public:
        ClassInit(int size){ 
            // Static constructor definition
            str = new char[size];
            str = "How are you?";
        }
    } initializer;
};

// Static variable creation
char* ClassStatic::str; 
// Static constructor call
ClassStatic::ClassInit ClassStatic::initializer(20);

int main() {
    ClassStatic a;
    ClassStatic b;
    std::cout << "String in a: " << a.get_str() << std::endl;
    std::cout << "String in b: " << b.get_str() << std::endl;
    a.set_str("I am fine");
    std::cout << "String in a: " << a.get_str() << std::endl;
    std::cout << "String in b: " << b.get_str() << std::endl;
    std::cin.ignore();
}

เอาท์พุท:

String in a: How are you?
String in b: How are you?
String in a: I am fine
String in b: I am fine

เหตุใดคุณจึงต้องnewป้อนอักขระตัวเดียวเพื่อรั่วตัวชี้และเขียนทับทันที?
Eric

0

ว้าวฉันไม่อยากจะเชื่อเลยว่าไม่มีใครพูดถึงคำตอบที่ชัดเจนที่สุดและสิ่งที่เลียนแบบพฤติกรรมของคอนสตรัคเตอร์แบบคงที่ C # อย่างใกล้ชิดที่สุดนั่นคือมันจะไม่ถูกเรียกจนกระทั่งวัตถุแรกของประเภทนั้นถูกสร้างขึ้น

std::call_once()มีอยู่ใน C ++ 11; ถ้าคุณไม่สามารถใช้มันก็สามารถทำได้ด้วยตัวแปรบูลีนสแตติกคลาสและการเปรียบเทียบและการแลกเปลี่ยนอะตอมมิก ใน Constructor ของคุณดูว่าคุณสามารถเปลี่ยนแฟล็กแบบสแตติกคลาสจากfalseเป็นtrueหรือไม่ถ้าเป็นเช่นนั้นคุณสามารถรันโค้ดสแตติกแบบสแตติกได้

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

ในกรณีที่ไม่มี C ++ 11 สิ่งนี้จะช่วยให้คุณเริ่มต้นได้

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

enum EStaticConstructor { kNotRun, kRunning, kDone };
static volatile EStaticConstructor sm_eClass = kNotRun;

และสิ่งนี้ในตัวสร้างของคุณ:

while (sm_eClass == kNotRun)
{
    if (atomic_compare_exchange_weak(&sm_eClass, kNotRun, kRunning))
    {
        /* Perform static initialization here. */

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