รูปแบบการออกแบบ C ++ Singleton


735

เมื่อเร็ว ๆ นี้ฉันได้พบกับการตระหนักถึง / การใช้รูปแบบการออกแบบซิงเกิลตันสำหรับ C ++ มันมีลักษณะเช่นนี้ (ฉันยอมรับมันจากตัวอย่างในชีวิตจริง):

// a lot of methods are omitted here
class Singleton
{
   public:
       static Singleton* getInstance( );
       ~Singleton( );
   private:
       Singleton( );
       static Singleton* instance;
};

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

คำถามหลักของฉันคือฉันจะใช้มันอย่างถูกวิธีได้อย่างไร



10
คุณจะพบกับการอภิปรายที่ยอดเยี่ยมเกี่ยวกับวิธีใช้งานซิงเกิลตันพร้อมกับความปลอดภัยของเธรดใน C ++ ในบทความนี้ aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf

106
@sbi - ข้อเสนอ Sith เท่านั้นในข้อตกลง ปัญหาส่วนใหญ่สามารถแก้ไขได้โดยไม่ต้องใช้ซิงเกิลตันหรือไม่? อย่างแน่นอน ซิงเกิลทำให้เกิดปัญหาของตนเองหรือไม่? ใช่. อย่างไรก็ตามฉันไม่สามารถพูดได้อย่างตรงไปตรงมาว่าพวกเขาไม่ดีเนื่องจากการออกแบบนั้นเกี่ยวกับการพิจารณาถึงการแลกเปลี่ยนและเข้าใจถึงความแตกต่างของวิธีการของคุณ
derekerdmann

11
@derekerdmann: ผมไม่ได้บอกว่าคุณไม่จำเป็นต้องตัวแปรทั่วโลก (และเมื่อคุณต้องการหนึ่งซิงเกิลบางครั้งจะดีกว่า) สิ่งที่ฉันพูดคือควรใช้ให้น้อยที่สุด Glorifying Singleton เป็นรูปแบบการออกแบบที่มีค่าให้ความรู้สึกที่ดีต่อการใช้งานมากกว่าการแฮ็กทำให้การเขียนโค้ดยากต่อการบำรุงรักษาและทดสอบได้ยาก นี่คือเหตุผลที่ฉันโพสต์ความคิดเห็นของฉัน ไม่มีสิ่งใดที่คุณพูดไปไกลขนาดนี้
sbi

13
@sbi: สิ่งที่คุณพูดคือ "อย่าใช้มัน" ไม่ใช่สิ่งที่สมเหตุสมผล "พวกเขาควรจะใช้ให้น้อยที่สุดเท่าที่จะเป็นไปได้" ที่คุณเปลี่ยนไปในภายหลัง - แน่นอนคุณเห็นความแตกต่าง
jwd

คำตอบ:


1106

ในปี 2008 ฉันให้การใช้งาน C ++ 98 ของรูปแบบการออกแบบซิงเกิลตันที่ได้รับการประเมินอย่างเชื่องช้ารับประกันการทำลายล้างไม่ใช่เทคนิคที่ปลอดภัยสำหรับเธรด: ผู้
ใดสามารถส่งตัวอย่างของซิงเกิลตันใน c ++ ได้หรือไม่

นี่คือการปรับปรุง C ++ 11 การดำเนินการตามรูปแบบการออกแบบโทนที่เป็นคนขี้เกียจ-ประเมินอย่างถูกต้องถูกทำลายและด้ายปลอดภัย

class S
{
    public:
        static S& getInstance()
        {
            static S    instance; // Guaranteed to be destroyed.
                                  // Instantiated on first use.
            return instance;
        }
    private:
        S() {}                    // Constructor? (the {} brackets) are needed here.

        // C++ 03
        // ========
        // Don't forget to declare these two. You want to make sure they
        // are unacceptable otherwise you may accidentally get copies of
        // your singleton appearing.
        S(S const&);              // Don't Implement
        void operator=(S const&); // Don't implement

        // C++ 11
        // =======
        // We can use the better technique of deleting the methods
        // we don't want.
    public:
        S(S const&)               = delete;
        void operator=(S const&)  = delete;

        // Note: Scott Meyers mentions in his Effective Modern
        //       C++ book, that deleted functions should generally
        //       be public as it results in better error messages
        //       due to the compilers behavior to check accessibility
        //       before deleted status
};

ดูบทความนี้เกี่ยวกับเวลาที่จะใช้ซิงเกิล: (ไม่บ่อย)
Singleton: ควรใช้อย่างไร

ดูบทความทั้งสองนี้เกี่ยวกับลำดับการเริ่มต้นและวิธีรับมือ:
ตัวแปรแบบคงที่การเริ่มต้นคำสั่งการ
ค้นหาปัญหาการเริ่มต้นแบบคงที่ C ++

ดูบทความนี้อธิบายอายุการใช้งาน:
อะไรคืออายุการใช้งานของตัวแปรแบบคงที่ในฟังก์ชัน C ++?

ดูบทความนี้ที่กล่าวถึงผลกระทบของการเธรดบางอย่างกับ singletons:
อินสแตนซ์ Singleton ที่ประกาศเป็นตัวแปรแบบคงที่ของวิธี GetInstance มันเป็นเธรดที่ปลอดภัยหรือไม่

ดูบทความนี้ที่อธิบายว่าทำไมการล็อกที่ถูกตรวจสอบซ้ำสองครั้งจะไม่ทำงานใน C ++:
พฤติกรรมที่ไม่ได้กำหนดทั่วไปที่โปรแกรมเมอร์ C ++ ควรรู้คืออะไร?
ดร. ดอบส์: C ++ และภัยคุกคามของการล็อคเช็คที่สอง: ตอนที่ 1


23
คำตอบที่ดี. แต่ควรทราบว่านี้ไม่ด้ายปลอดภัยstackoverflow.com/questions/1661529/...
วรุณ

4
@zourtney: หลายคนไม่ได้ตระหนักถึงสิ่งที่คุณก็ไม่ได้ :)
โยฮันน์ Gerell

4
@ MaximYegorushkin: เมื่อสิ่งนี้ถูกทำลายมีการกำหนดไว้เป็นอย่างดี (ไม่มีความกำกวม) ดู: stackoverflow.com/questions/246564/…
Martin York

3
What irks me most though is the run-time check of the hidden boolean in getInstance()นั่นคือข้อสมมติเกี่ยวกับเทคนิคการนำไปใช้ ไม่จำเป็นต้องมีสมมติฐานว่ามันมีชีวิตอยู่ ดูstackoverflow.com/a/335746/14065คุณสามารถบังคับสถานการณ์เพื่อให้มีชีวิตอยู่เสมอ (ค่าใช้จ่ายน้อยกว่าSchwarz counter) ตัวแปรโกลบอลมีปัญหาเพิ่มเติมเกี่ยวกับการเริ่มต้น (ข้ามหน่วยการรวบรวม) เนื่องจากคุณไม่ได้บังคับให้สั่ง ข้อดีของรุ่นนี้คือ 1) การเริ่มต้นขี้เกียจ 2) ความสามารถในการบังคับใช้คำสั่ง (Schwarz ช่วย แต่เป็นสิ่งที่น่าเกลียด) ใช่แล้วget_instance()ขี้เหร่มาก
Martin York

3
@kol: ไม่มันไม่ใช่แบบปกติ เพียงเพราะเริ่มต้นคัดลอกและวางรหัสโดยไม่ต้องคิดไม่ได้ทำให้มันเป็นปกติ คุณควรดูกรณีการใช้งานเสมอและตรวจสอบให้แน่ใจว่าผู้ดำเนินการที่ได้รับมอบหมายทำในสิ่งที่คาดหวัง การคัดลอกและวางรหัสจะนำคุณไปสู่ข้อผิดพลาด
Martin York

47

การเป็นซิงเกิลคุณมักไม่ต้องการให้ถูกทำลาย

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


4
ถ้าการลบไม่ถูกเรียกอย่างชัดเจนบนอินสแตนซ์ Singleton * แบบคงที่สิ่งนี้จะไม่ถือว่าเป็นการรั่วของหน่วยความจำหรือไม่?
Andrew Garrison

7
ไม่ใช่หน่วยความจำรั่วอีกต่อไปกว่าการประกาศตัวแปรทั่วโลกอย่างง่าย ๆ
ilya n

15
เมื่อต้องการตั้งค่าบางสิ่งบางอย่าง ... "การรั่วไหลของหน่วยความจำ" เกี่ยวข้องกับซิงเกิลตันแบบเผชิญหน้านั้นไม่เกี่ยวข้องอย่างสมบูรณ์ หากคุณมีทรัพยากรที่เป็นรัฐซึ่งมีคำสั่งซื้อโครงสร้างการปกครองมีความสำคัญซิงเกิลอาจเป็นอันตรายได้ แต่หน่วยความจำทั้งหมดได้รับการกู้คืนอย่างสมบูรณ์โดยระบบปฏิบัติการเมื่อมีการยกเลิกโปรแกรม ... ทำให้ประเด็นทางวิชาการทั้งหมดเป็นโมฆะใน 99.9% ของกรณีทั้งหมด ถ้าคุณต้องการโต้เถียงไวยากรณ์กลับไปกลับมาของสิ่งที่เป็นและไม่ใช่ "หน่วยความจำรั่ว" นั่นก็ดี แต่ตระหนักว่ามันเป็นสิ่งที่ทำให้ไขว้เขวจากการตัดสินใจออกแบบจริง
jkerian

12
@ jkerian: การรั่วไหลและการทำลายของหน่วยความจำในบริบท C ++ ไม่ได้เกี่ยวกับการรั่วไหลของหน่วยความจำ จริงๆแล้วมันเกี่ยวกับการควบคุมทรัพยากร หากคุณรั่วไหลหน่วยความจำจะไม่เรียกตัวจัดตำแหน่งและทำให้ทรัพยากรใด ๆ ที่เกี่ยวข้องกับวัตถุนั้นไม่ได้รับการเผยแพร่อย่างถูกต้อง หน่วยความจำเป็นเพียงตัวอย่างง่ายๆที่เราใช้เมื่อสอนการเขียนโปรแกรม แต่มีแหล่งข้อมูลที่ซับซ้อนมากขึ้น
Martin York

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

38

คุณสามารถหลีกเลี่ยงการจัดสรรหน่วยความจำ มีตัวแปรหลายตัวทุกตัวมีปัญหาในกรณีที่มีสภาพแวดล้อมแบบมัลติเธรด

ฉันชอบการนำไปใช้แบบนี้ (จริง ๆ แล้วมันไม่ถูกต้องว่าฉันชอบเพราะฉันหลีกเลี่ยงซิงเกิลมากที่สุด):

class Singleton
{
private:
   Singleton();

public:
   static Singleton& instance()
   {
      static Singleton INSTANCE;
      return INSTANCE;
   }
};

ไม่มีการจัดสรรหน่วยความจำแบบไดนามิก


3
ในบางกรณีการกำหนดค่าเริ่มต้นที่ขี้เกียจนี้ไม่ใช่รูปแบบในอุดมคติที่จะติดตาม ตัวอย่างหนึ่งคือถ้านวกรรมิกของซิงเกิลจัดสรรหน่วยความจำจากฮีปและคุณต้องการให้การจัดสรรนั้นสามารถคาดเดาได้เช่นในระบบฝังตัวหรือสภาพแวดล้อมอื่นที่มีการควบคุมอย่างเข้มงวด ฉันชอบเมื่อรูปแบบ Singleton เป็นรูปแบบที่ดีที่สุดที่จะใช้ในการสร้างอินสแตนซ์เป็นสมาชิกแบบคงที่ของชั้นเรียน
dma

3
สำหรับโปรแกรมขนาดใหญ่จำนวนมากโดยเฉพาะโปรแกรมที่มีไลบรารีแบบไดนามิก อ็อบเจ็กต์โกลบอลหรือสแตติกใด ๆ ที่ไม่มีแบบดั้งเดิมสามารถนำไปสู่ ​​segfaults / ล่มเมื่อออกจากโปรแกรมบนแพลตฟอร์มจำนวนมากเนื่องจากลำดับของปัญหาการทำลายเมื่อไลบรารีไม่โหลด นี่เป็นหนึ่งในเหตุผลหลาย ๆ ข้อตกลงในการเข้ารหัส (รวมถึงของ Google) ที่ห้ามการใช้วัตถุคงที่และที่ไม่สำคัญ
obecalp

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

อะไรทำให้ผู้ใช้ไม่สามารถกำหนดสิ่งนี้ให้กับวัตถุหลาย ๆ ชิ้นที่คอมไพเลอร์เบื้องหลังใช้ตัวสร้างสำเนาของตัวเอง
Tony Tannous

19

คำตอบของ @Loki Astariนั้นยอดเยี่ยม

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

ในกรณีนี้std::shared_ptrสามารถใช้เพื่อรักษาซิงเกิลตันให้กับผู้ใช้ทุกคนแม้ในขณะที่มีการเรียก destructors แบบคงที่ในตอนท้ายของโปรแกรม:

class Singleton
{
public:
    Singleton(Singleton const&) = delete;
    Singleton& operator=(Singleton const&) = delete;

    static std::shared_ptr<Singleton> instance()
    {
        static std::shared_ptr<Singleton> s{new Singleton};
        return s;
    }

private:
    Singleton() {}
};

9

ทางเลือกอื่นที่ไม่จัดสรร: สร้างซิงเกิลตันของคลาสCตามที่คุณต้องการ:

singleton<C>()

การใช้

template <class X>
X& singleton()
{
    static X x;
    return x;
}

ทั้งคำตอบนี้และคำตอบของCătălinนั้นปลอดภัยสำหรับเธรดโดยอัตโนมัติใน C ++ ปัจจุบัน แต่จะอยู่ใน C ++ 0x


ในปัจจุบันภายใต้ gcc มันเป็นหัวข้อที่ปลอดภัย (และได้รับในขณะที่)
Martin York

13
ปัญหาของการออกแบบนี้คือถ้าใช้กับหลาย ๆ ไลบรารี แต่ละห้องสมุดมีสำเนาของซิงเกิลตันที่ห้องสมุดใช้ ดังนั้นมันจึงไม่ใช่ซิงเกิลอีกต่อไป
Martin York

6

ฉันไม่พบการติดตั้ง CRTP ในคำตอบดังนั้นนี่คือ:

template<typename HeirT>
class Singleton
{
public:
    Singleton() = delete;

    Singleton(const Singleton &) = delete;

    Singleton &operator=(const Singleton &) = delete;

    static HeirT &instance()
    {
        static HeirT instance;
        return instance;
    }
};

หากต้องการใช้เพียงแค่สืบทอดคลาสของคุณจากสิ่งนี้เช่น: class Test : public Singleton<Test>


1
ไม่สามารถทำให้สิ่งนี้ทำงานกับ C ++ 17 ได้จนกว่าฉันจะสร้าง Constructor เริ่มต้นและ '= default;'
WFranczyk

6

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

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

นั่นคือเหตุผลที่ฉันชอบซิงเกิล - ฮีปที่ได้รับการจัดสรร ผมให้ชัดเจนinit()และterm()วิธีการสำหรับการ singletons mainทั้งหมดและเรียกพวกเขาภายใน ดังนั้นฉันจึงสามารถควบคุมลำดับการสร้าง / การทำลายเดี่ยวและฉันรับประกันได้ว่าจะสร้างซิงเกิลไม่ว่าจะมีใครโทรมาgetInstance()หรือไม่ก็ตาม


2
หากคุณอ้างถึงคำตอบที่ยอมรับในปัจจุบันคำสั่งแรกนั้นผิด destructor ไม่ถูกเรียกจนกว่าวัตถุระยะเวลาเก็บแบบคงที่ทั้งหมดจะถูกทำลาย
Martin York

5

นี่คือการใช้งานที่ง่าย

#include <Windows.h>
#include <iostream>

using namespace std;


class SingletonClass {

public:
    static SingletonClass* getInstance() {

    return (!m_instanceSingleton) ?
        m_instanceSingleton = new SingletonClass : 
        m_instanceSingleton;
    }

private:
    // private constructor and destructor
    SingletonClass() { cout << "SingletonClass instance created!\n"; }
    ~SingletonClass() {}

    // private copy constructor and assignment operator
    SingletonClass(const SingletonClass&);
    SingletonClass& operator=(const SingletonClass&);

    static SingletonClass *m_instanceSingleton;
};

SingletonClass* SingletonClass::m_instanceSingleton = nullptr;



int main(int argc, const char * argv[]) {

    SingletonClass *singleton;
    singleton = singleton->getInstance();
    cout << singleton << endl;

    // Another object gets the reference of the first object!
    SingletonClass *anotherSingleton;
    anotherSingleton = anotherSingleton->getInstance();
    cout << anotherSingleton << endl;

    Sleep(5000);

    return 0;
}

มีเพียงวัตถุเดียวที่สร้างขึ้นและการอ้างอิงวัตถุนี้จะถูกส่งคืนในภายหลังทุกครั้ง

SingletonClass instance created!
00915CB8
00915CB8

ที่นี่ 00915CB8 เป็นตำแหน่งหน่วยความจำของ Singleton Object เหมือนกันกับช่วงเวลาของโปรแกรม แต่ (ปกติ!) จะแตกต่างกันในแต่ละครั้งที่โปรแกรมทำงาน

NB นี้ไม่ได้เป็นหัวข้อที่ปลอดภัยคุณต้องให้แน่ใจว่าหัวข้อความปลอดภัย


5

หากคุณต้องการจัดสรรวัตถุในกองทำไมไม่ใช้ตัวชี้ที่ไม่ซ้ำกัน หน่วยความจำจะถูกยกเลิกการจัดสรรเนื่องจากเราใช้ตัวชี้เฉพาะ

class S
{
    public:
        static S& getInstance()
        {
            if( m_s.get() == 0 )
            {
              m_s.reset( new S() );
            }
            return *m_s;
        }

    private:
        static std::unique_ptr<S> m_s;

        S();
        S(S const&);            // Don't Implement
        void operator=(S const&); // Don't implement
};

std::unique_ptr<S> S::m_s(0);

3
เลิกใช้ใน c ++ 11 แนะนำให้ใช้ unique_ptr แทน cplusplus.com/reference/memory/auto_ptr cplusplus.com/reference/memory/unique_ptr
Andrew

2
กระทู้นี้ไม่ปลอดภัย ดีกว่าที่จะทำให้m_sท้องถิ่นstaticของgetInstance()และเริ่มต้นได้ทันทีโดยไม่ต้องมีการทดสอบ
Galik

2

แน่นอนมันอาจถูกจัดสรรจากกอง แต่ไม่มีแหล่งที่มาไม่มีทางรู้

การใช้งานทั่วไป (นำมาจากรหัสบางอย่างที่ฉันมีใน emacs แล้ว) จะเป็น:

Singleton * Singleton::getInstance() {
    if (!instance) {
        instance = new Singleton();
    };
    return instance;
};

... และพึ่งพาโปรแกรมที่ออกนอกขอบเขตเพื่อทำความสะอาดในภายหลัง

หากคุณทำงานบนแพลตฟอร์มที่ต้องทำการล้างข้อมูลด้วยตนเองฉันอาจเพิ่มขั้นตอนการล้างข้อมูลด้วยตนเอง

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


คุณสามารถอนุมานเนื่องจากคุณสามารถเห็นตัวแปรอินสแตนซ์นั้นเป็นตัวชี้ไปยังอินสแตนซ์ของคลาส
Artem Barger

3
ไม่จำเป็นต้องจัดสรรซิงเกิลแบบไดนามิก ในความเป็นจริงนี่เป็นความคิดที่ไม่ดีเนื่องจากไม่มีวิธีการจัดสรรโดยอัตโนมัติโดยใช้การออกแบบด้านบน ปล่อยให้มันหลุดออกจากขอบเขตไม่ได้เรียกว่า destructors และขี้เกียจ
Martin York

คุณสามารถยกเลิกการจัดสรรโดยอัตโนมัติโดยใช้ฟังก์ชั่น atexit นั่นคือสิ่งที่เราทำ (ไม่ได้บอกว่ามันเป็นความคิดที่ดี)
โจ

2

มีใครพูดถึงstd::call_onceและstd::once_flag? วิธีการอื่น ๆ ส่วนใหญ่ - รวมถึงการตรวจสอบการล็อคซ้ำ - หัก

ปัญหาสำคัญอย่างหนึ่งในการใช้รูปแบบซิงเกิลคือการเริ่มต้นอย่างปลอดภัย วิธีเดียวที่ปลอดภัยคือการป้องกันลำดับการเริ่มต้นด้วยการซิงโครไนซ์อุปสรรค แต่อุปสรรคเหล่านั้นเองก็ต้องเริ่มต้นอย่างปลอดภัย std::once_flagเป็นกลไกในการรับประกันการเริ่มต้นอย่างปลอดภัย


2

เราไปที่หัวข้อนี้เมื่อไม่นานมานี้ในชั้นเรียน EECS ของฉัน หากคุณต้องการดูรายละเอียดของเอกสารประกอบการบรรยายโปรดไปที่http://umich.edu/~eecs381/lecture/IdiomsDesPattsCresh.pdf

มีสองวิธีที่ฉันรู้ในการสร้างคลาส Singleton อย่างถูกต้อง

วิธีแรก:

ใช้มันคล้ายกับที่คุณมีในตัวอย่าง สำหรับการทำลาย "Singletons มักจะทนต่อความยาวของการเรียกใช้โปรแกรมระบบปฏิบัติการส่วนใหญ่จะกู้คืนหน่วยความจำและทรัพยากรอื่น ๆ ส่วนใหญ่เมื่อโปรแกรมหยุดทำงานดังนั้นจึงมีข้อโต้แย้งว่า

อย่างไรก็ตามเป็นวิธีปฏิบัติที่ดีในการล้างข้อมูลเมื่อสิ้นสุดโปรแกรม ดังนั้นคุณสามารถทำเช่นนี้กับคลาส SingletonDestructor คงที่เสริมและประกาศว่าเป็นเพื่อนใน Singleton ของคุณ

class Singleton {
public:
  static Singleton* get_instance();

  // disable copy/move -- this is a Singleton
  Singleton(const Singleton&) = delete;
  Singleton(Singleton&&) = delete;
  Singleton& operator=(const Singleton&) = delete;
  Singleton& operator=(Singleton&&) = delete;

  friend class Singleton_destroyer;

private:
  Singleton();  // no one else can create one
  ~Singleton(); // prevent accidental deletion

  static Singleton* ptr;
};

// auxiliary static object for destroying the memory of Singleton
class Singleton_destroyer {
public:
  ~Singleton_destroyer { delete Singleton::ptr; }
};

Singleton_destroyer จะถูกสร้างขึ้นเมื่อเริ่มต้นโปรแกรมและ "เมื่อโปรแกรมสิ้นสุดลงวัตถุโกลบอล / สแตติกทั้งหมดจะถูกทำลายโดยรหัสปิดไลบรารีของไลบรารีรันไทม์ (แทรกโดยตัวเชื่อมโยง) ดังนั้นตัวทำลายจะถูกทำลายตัวทำลายจะลบ Singleton ออก destructor."

วิธีที่สอง

สิ่งนี้เรียกว่า Meyers Singleton สร้างโดย C ++ wizard Meyers Scott เพียงกำหนด get_instance () ให้แตกต่างกัน ตอนนี้คุณสามารถกำจัดตัวแปรสมาชิกพอยน์เตอร์ได้

// public member function
static Singleton& Singleton::get_instance()
{
  static Singleton s;
  return s;
}

นี่เป็นระเบียบเพราะค่าที่ส่งคืนเป็นการอ้างอิงและคุณสามารถใช้.ไวยากรณ์แทน->การเข้าถึงตัวแปรสมาชิก

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

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


1

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

struct Store{
   std::array<Something, 1024> data;
   size_t get(size_t idx){ /* ... */ }
   void incr_ref(size_t idx){ /* ... */}
   void decr_ref(size_t idx){ /* ... */}
};

template<Store* store_p>
struct ItemRef{
   size_t idx;
   auto get(){ return store_p->get(idx); };
   ItemRef() { store_p->incr_ref(idx); };
   ~ItemRef() { store_p->decr_ref(idx); };
};

Store store1_g;
Store store2_g; // we don't restrict the number of global Store instances

ตอนนี้ภายในฟังก์ชัน (เช่นmain) คุณสามารถทำได้:

auto ref1_a = ItemRef<&store1_g>(101);
auto ref2_a = ItemRef<&store2_g>(201); 

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

หากStoreตัวเองเป็นคลาส templated สิ่งต่าง ๆ ก็จะยุ่งเหยิง แต่ก็ยังคงเป็นไปได้ที่จะใช้วิธีนี้บางทีโดยการใช้คลาสตัวช่วยที่มีลายเซ็นต่อไปนี้:

template <typename Store_t, Store_t* store_p>
struct StoreWrapper{ /* stuff to access store_p, e.g. methods returning 
                       instances of ItemRef<Store_t, store_p>. */ };

ขณะนี้ผู้ใช้สามารถสร้างStoreWrapperประเภท (และอินสแตนซ์ส่วนกลาง) สำหรับแต่ละStoreอินสแตนซ์ส่วนกลางและเข้าถึงร้านค้าผ่านอินสแตนซ์ wrapper ของพวกเขาStoreได้ตลอดเวลา


0

นี่เป็นเรื่องเกี่ยวกับการจัดการตลอดชีวิตของวัตถุ สมมติว่าคุณมีมากกว่าหนึ่งตันในซอฟต์แวร์ของคุณ และพวกมันก็ขึ้นอยู่กับ Logger singleton ในระหว่างการทำลายแอปพลิเคชันสมมติว่าวัตถุซิงเกิลตันอื่นใช้ Logger เพื่อบันทึกขั้นตอนการทำลาย คุณต้องรับประกันว่า Logger ควรได้รับการทำความสะอาดครั้งสุดท้าย ดังนั้นโปรดตรวจสอบกระดาษนี้: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf


0

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

#pragma once

#include <memory>

template<typename T>
class Singleton
{
private:
  static std::weak_ptr<T> _singleton;
public:
  static std::shared_ptr<T> singleton()
  {
    std::shared_ptr<T> singleton = _singleton.lock();
    if (!singleton) 
    {
      singleton.reset(new T());
      _singleton = singleton;
    }

    return singleton;
  }
};

template<typename T>
std::weak_ptr<T> Singleton<T>::_singleton;

0

รหัสของคุณถูกต้องยกเว้นว่าคุณไม่ได้ประกาศตัวชี้อินสแตนซ์นอกชั้นเรียน การประกาศคลาสภายในของตัวแปรสแตติกไม่ถือเป็นการประกาศใน C ++ อย่างไรก็ตามสิ่งนี้ได้รับอนุญาตในภาษาอื่นเช่นC #หรือJavaเป็นต้น

class Singleton
{
   public:
       static Singleton* getInstance( );
   private:
       Singleton( );
       static Singleton* instance;
};
Singleton* Singleton::instance; //we need to declare outside because static variables are global

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


-1

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


2
แต่น่าเสียดายที่ไม่ได้. สิ่งนี้ได้ถูกกล่าวถึงในเชิงลึกโดยนักพัฒนา C ++ ที่ดีที่สุด การล็อคที่ถูกตรวจสอบซ้ำถูกทำลายใน C ++ 03
Martin York

-1
#define INS(c) private:void operator=(c const&){};public:static c& I(){static c _instance;return _instance;}

ตัวอย่าง:

   class CCtrl
    {
    private:
        CCtrl(void);
        virtual ~CCtrl(void);

    public:
        INS(CCtrl);

-1

คลาส singleton แบบง่ายต้องเป็นไฟล์คลาสส่วนหัวของคุณ

#ifndef SC_SINGLETON_CLASS_H
#define SC_SINGLETON_CLASS_H

class SingletonClass
{
    public:
        static SingletonClass* Instance()
        {
           static SingletonClass* instance = new SingletonClass();
           return instance;
        }

        void Relocate(int X, int Y, int Z);

    private:
        SingletonClass();
        ~SingletonClass();
};

#define sSingletonClass SingletonClass::Instance()

#endif

เข้าถึงซิงเกิลของคุณแบบนี้:

sSingletonClass->Relocate(1, 2, 5);

-3

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

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