Mẫu thiết kế Singleton C ++


736

Gần đây, tôi đã tình cờ nhận ra / triển khai mẫu thiết kế Singleton cho C ++. Nó trông giống như thế này (tôi đã áp dụng nó từ ví dụ thực tế):

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

Từ tuyên bố này, tôi có thể suy ra rằng trường đối tượng được bắt đầu trên heap. Điều đó có nghĩa là có sự phân bổ bộ nhớ. Điều hoàn toàn không rõ ràng đối với tôi là khi chính xác bộ nhớ sẽ bị giải phóng? Hoặc có một lỗi và rò rỉ bộ nhớ? Có vẻ như có một vấn đề trong việc thực hiện.

Câu hỏi chính của tôi là, làm thế nào để tôi thực hiện nó đúng cách?



10
Bạn sẽ tìm thấy một cuộc thảo luận tuyệt vời về cách triển khai một đơn, cùng với tính an toàn của luồng trong C ++ trong bài viết này. aristeia.com/Papers/DDJ%5FJul%5FAug%5F2004%5Frevised.pdf

106
@sbi - Chỉ có một Sith thỏa thuận trong tuyệt đối. Phần lớn các vấn đề có thể được giải quyết mà không có Singletons? Chắc chắn rồi. Do Singletons gây ra vấn đề của riêng họ? Đúng. Tuy nhiên, tôi không thể thành thật nói rằng họ xấu , vì thiết kế hoàn toàn là xem xét sự đánh đổi và hiểu được các sắc thái trong cách tiếp cận của bạn.
derekerdmann

11
@derekerdmann: Tôi không nói rằng bạn không bao giờ cần một biến toàn cục (và khi bạn cần một biến số, đôi khi Singleton sẽ tốt hơn). Điều tôi nói là chúng nên được sử dụng ít nhất có thể. Tôn vinh Singleton như một mẫu thiết kế có giá trị mang lại ấn tượng khi sử dụng nó, thay vì đó là một bản hack , làm cho mã khó hiểu, khó bảo trì và khó kiểm tra. Đây là lý do tại sao tôi đăng bình luận của tôi. Không có gì bạn nói cho đến nay mâu thuẫn với điều này.
sbi

13
@sbi: Điều bạn nói là "Đừng sử dụng chúng." Không phải là hợp lý hơn "chúng nên được sử dụng ít nhất có thể" mà sau này bạn đổi thành - chắc chắn bạn sẽ thấy sự khác biệt.
JWD

Câu trả lời:


1106

Vào năm 2008, tôi đã cung cấp một triển khai C ++ 98 cho mẫu thiết kế Singleton được đánh giá lười biếng, phá hủy được bảo đảm, không an toàn về mặt kỹ thuật:
Có ai có thể cung cấp cho tôi một mẫu Singleton trong c ++ không?

Dưới đây là bản triển khai C ++ 11 được cập nhật của mẫu thiết kế Singleton được đánh giá lười biếng, bị phá hủy chính xác và an toàn cho luồng .

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
};

Xem bài viết này về thời điểm sử dụng singleton: (không thường xuyên)
Singleton: Nên sử dụng nó như thế nào

Xem hai bài viết này về thứ tự khởi tạo và cách đối phó:
Thứ tự khởi tạo biến tĩnh
Tìm kiếm các vấn đề về thứ tự khởi tạo tĩnh C ++

Xem bài viết này mô tả về thời gian sống: Thời
gian tồn tại của biến tĩnh trong hàm C ++ là bao nhiêu?

Xem bài viết này thảo luận về một số hàm ý luồng cho singletons:
Ví dụ Singleton được khai báo là biến tĩnh của phương thức GetInstance, nó có an toàn cho luồng không?

Xem bài viết này giải thích tại sao khóa kiểm tra kép sẽ không hoạt động trên C ++:
Tất cả các hành vi không xác định phổ biến mà lập trình viên C ++ nên biết là gì?
Tiến sĩ Dobbs: C ++ và Những nguy hiểm của việc khóa hai lần: Phần I


23
Câu trả lời tốt. Nhưng nên lưu ý rằng đây không phải là stackoverflow.com/questions/1661529/ cấp
Varuna

4
@zourtney: Nhiều người không nhận ra những gì bạn vừa làm :)
Johann Gerell

4
@MaximYegorushkin: Khi điều này bị phá hủy được xác định rất rõ (không có sự mơ hồ). Xem: stackoverflow.com/questions/246564/
Martin York

3
What irks me most though is the run-time check of the hidden boolean in getInstance()Đó là một giả định về kỹ thuật thực hiện. Không cần phải có giả định về việc nó còn sống. xem stackoverflow.com/a/335746/14065 Bạn có thể buộc một tình huống để nó luôn tồn tại (ít chi phí hơn Schwarz counter). Các biến toàn cục có nhiều vấn đề hơn với thứ tự khởi tạo (trên các đơn vị biên dịch) khi bạn không bắt buộc một đơn đặt hàng. Ưu điểm của mô hình này là 1) khởi tạo lười biếng. 2) Khả năng thực thi một mệnh lệnh (Schwarz giúp nhưng xấu hơn). Yep get_instance()xấu hơn nhiều.
Martin York

3
@kol: Không. Nó không phải là thông thường. Chỉ vì người mới bắt đầu sao chép và dán mã mà không suy nghĩ không biến nó thành mã thông thường. Bạn phải luôn luôn nhìn vào ca sử dụng và đảm bảo toán tử gán thực hiện những gì được mong đợi. Sao chép và dán mã sẽ dẫn bạn đến lỗi.
Martin York

47

Là một người độc thân, bạn thường không muốn nó bị phá hủy.

Nó sẽ bị phá hỏng và bị hủy bỏ khi chương trình kết thúc, đó là hành vi bình thường, mong muốn đối với một người độc thân. Nếu bạn muốn có thể làm sạch nó một cách rõ ràng, thật dễ dàng để thêm một phương thức tĩnh vào lớp cho phép bạn khôi phục nó về trạng thái sạch và để nó được phân bổ lại vào lần tới, nhưng nó nằm ngoài phạm vi của một Singleton "cổ điển".


4
nếu xóa không bao giờ được gọi một cách rõ ràng trên cá thể Singleton * tĩnh, thì về mặt kỹ thuật này có còn bị coi là rò rỉ bộ nhớ không?
Andrew Garrison

7
Nó không phải là một rò rỉ bộ nhớ nữa ngoài một tuyên bố đơn giản về một biến toàn cục.
ilya n.

15
Để đặt một cái gì đó thẳng ... "rò rỉ bộ nhớ" liên quan đến các singletons vis-a-vis là hoàn toàn không liên quan. Nếu bạn có tài nguyên trạng thái trong đó thứ tự giải cấu trúc quan trọng, singletons có thể nguy hiểm; nhưng tất cả bộ nhớ được lấy lại một cách sạch sẽ bởi hệ điều hành khi chấm dứt chương trình ... vô hiệu hóa điểm hoàn toàn học thuật này trong 99,9% trường hợp. Nếu bạn muốn tranh luận về ngữ pháp qua lại về những gì và không phải là "rò rỉ bộ nhớ", thì tốt thôi, nhưng nhận ra rằng đó là một sự phân tâm từ các quyết định thiết kế thực tế.
jkerian

12
@jkerian: Rò rỉ và phá hủy bộ nhớ trong ngữ cảnh C ++ không thực sự là về rò rỉ bộ nhớ. Thực sự đó là về kiểm soát tài nguyên. Nếu bạn rò rỉ bộ nhớ, Destroctor không được gọi và do đó, bất kỳ tài nguyên nào liên quan đến đối tượng sẽ không được giải phóng chính xác. Bộ nhớ chỉ là ví dụ đơn giản mà chúng ta sử dụng khi dạy lập trình nhưng có nhiều tài nguyên phức tạp hơn ngoài kia.
Martin York

7
@Martin Tôi hoàn toàn đồng ý với bạn. Ngay cả khi tài nguyên duy nhất là bộ nhớ, bạn vẫn sẽ gặp rắc rối khi cố gắng tìm các rò rỉ THỰC SỰ trong chương trình của mình nếu bạn phải lội qua một danh sách các rò rỉ, lọc ra những thứ "không quan trọng". Tốt hơn là nên dọn sạch tất cả những thứ này để bất kỳ công cụ nào báo cáo rò rỉ chỉ báo cáo những điều đang là vấn đề.
Cá heo

38

Bạn có thể tránh phân bổ bộ nhớ. Có nhiều biến thể, tất cả đều có vấn đề trong trường hợp môi trường đa luồng.

Tôi thích loại triển khai này (thực ra, tôi không nói chính xác là tôi thích, vì tôi tránh các singletons càng nhiều càng tốt):

class Singleton
{
private:
   Singleton();

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

Nó không có phân bổ bộ nhớ động.


3
Trong một số trường hợp, khởi tạo lười biếng này không phải là mô hình lý tưởng để làm theo. Một ví dụ là nếu hàm tạo của singleton phân bổ bộ nhớ từ heap và bạn muốn phân bổ đó có thể dự đoán được, ví dụ như trong một hệ thống nhúng hoặc môi trường được kiểm soát chặt chẽ khác. Tôi thích, khi mẫu Singleton là mẫu tốt nhất để sử dụng, để tạo cá thể như một thành viên tĩnh của lớp.
dma

3
Đối với nhiều chương trình lớn hơn, đặc biệt là những chương trình có thư viện động. Bất kỳ đối tượng toàn cầu hoặc tĩnh nào không nguyên thủy đều có thể dẫn đến phân tách / sự cố khi thoát khỏi chương trình trên nhiều nền tảng do thứ tự các sự cố hủy khi thư viện dỡ tải. Đây là một trong những lý do khiến nhiều công ước mã hóa (bao gồm cả Google) cấm sử dụng các đối tượng tĩnh và toàn cầu không tầm thường.
obecalp

Có vẻ như trường hợp tĩnh trong triển khai như vậy có liên kết nội bộ và sẽ có các bản sao độc lập và độc lập trong các đơn vị dịch thuật khác nhau, điều này sẽ gây ra hành vi nhầm lẫn và sai. Nhưng tôi đã thấy nhiều triển khai như vậy, tôi có thiếu điều gì không?
FaceBro

Điều gì ngăn người dùng gán cái này cho nhiều đối tượng trong đó trình biên dịch đằng sau hậu trường sử dụng hàm tạo sao chép của chính nó?
Tony Tannous

19

Câu trả lời của @Loki Astari là tuyệt vời.

Tuy nhiên, có những lúc có nhiều đối tượng tĩnh mà bạn cần có thể đảm bảo rằng singleton sẽ không bị hủy cho đến khi tất cả các đối tượng tĩnh sử dụng singleton không còn cần nó nữa.

Trong trường hợp này std::shared_ptr có thể được sử dụng để giữ cho singleton tồn tại cho tất cả người dùng ngay cả khi các hàm hủy tĩnh đang được gọi ở cuối chương trình:

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

Một lựa chọn không phân bổ khác: tạo một singleton, nói về lớp C, khi bạn cần:

singleton<C>()

sử dụng

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

Cả câu trả lời này và Cătălin đều không tự động an toàn luồng trong C ++ hiện tại, nhưng sẽ ở C ++ 0x.


Hiện tại theo gcc, nó là chủ đề an toàn (và đã được một thời gian).
Martin York

13
Vấn đề với thiết kế này là nếu được sử dụng trên nhiều thư viện. Mỗi thư viện có một bản sao riêng của thư viện mà thư viện đó sử dụng. Vì vậy, nó không còn là một singleton.
Martin York

6

Tôi không tìm thấy triển khai CRTP trong số các câu trả lời, vì vậy đây là:

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;
    }
};

Để sử dụng chỉ cần kế thừa lớp của bạn từ đây, như: class Test : public Singleton<Test>


1
Không thể làm điều này hoạt động với C ++ 17 cho đến khi tôi tạo hàm tạo mặc định được bảo vệ và '= default;'.
WFranc Hot

6

Giải pháp trong câu trả lời được chấp nhận có một nhược điểm đáng kể - hàm hủy cho singleton được gọi sau khi điều khiển rời khỏi main()hàm. Có thể có vấn đề thực sự, khi một số đối tượng phụ thuộc được phân bổ bên trongmain .

Tôi đã gặp vấn đề này, khi cố gắng giới thiệu một Singleton trong ứng dụng Qt. Tôi quyết định rằng tất cả các hộp thoại thiết lập của tôi phải là Singletons và chấp nhận mô hình ở trên. Thật không may, lớp chính của Qt QApplicationđã được phân bổ trên stack trong mainhàm và Qt cấm tạo / hủy các hộp thoại khi không có đối tượng ứng dụng nào khả dụng.

Đó là lý do tại sao tôi thích singletons phân bổ heap. Tôi cung cấp một phương thức rõ ràng init()term()phương thức cho tất cả các singletons và gọi chúng bên trong main. Do đó, tôi có toàn quyền kiểm soát thứ tự tạo / hủy của singletons, và tôi cũng đảm bảo rằng các singletons sẽ được tạo ra, bất kể ai đó có gọi getInstance()hay không.


2
Nếu bạn đang đề cập đến câu trả lời hiện được chấp nhận, câu đầu tiên của bạn là sai. Hàm hủy không được gọi cho đến khi tất cả các đối tượng thời gian lưu trữ tĩnh bị hủy.
Martin York

5

Đây là một thực hiện dễ dàng.

#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;
}

Chỉ có một đối tượng được tạo và tham chiếu đối tượng này được trả về mỗi lần trả lời.

SingletonClass instance created!
00915CB8
00915CB8

Ở đây 00915CB8 là vị trí bộ nhớ của đối tượng singleton, tương tự với thời lượng của chương trình nhưng (thông thường!) Khác nhau mỗi khi chương trình được chạy.

NB Đây không phải là một chủ đề an toàn. Bạn phải đảm bảo an toàn chủ đề.


5

Nếu bạn muốn phân bổ đối tượng trong heap, tại sao không sử dụng một con trỏ duy nhất. Bộ nhớ cũng sẽ bị giải phóng vì chúng ta đang sử dụng một con trỏ duy nhất.

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
Không dùng nữa trong c ++ 11. unique_ptr được khuyến nghị thay thế. cplusplus.com/reference/memory/auto_ptr cplusplus.com/reference/memory/unique_ptr
Andrew

2
Đây không phải là chủ đề an toàn. Tốt hơn để làm cho m_smột địa phương staticcủa getInstance()và khởi tạo nó ngay lập tức mà không cần xét nghiệm.
Galik

2

Nó thực sự có thể được phân bổ từ đống, nhưng không có các nguồn thì không có cách nào để biết.

Việc triển khai điển hình (lấy từ một số mã tôi đã có trong emacs) sẽ là:

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

... Và dựa vào chương trình đi ra khỏi phạm vi để dọn dẹp sau đó.

Nếu bạn làm việc trên một nền tảng mà việc dọn dẹp phải được thực hiện thủ công, có lẽ tôi sẽ thêm một thói quen dọn dẹp thủ công.

Một vấn đề khác khi thực hiện theo cách này là nó không an toàn cho chuỗi. Trong một môi trường đa luồng, hai luồng có thể vượt qua "nếu" trước khi có cơ hội phân bổ thể hiện mới (vì vậy cả hai sẽ). Đây vẫn không phải là một thỏa thuận quá lớn nếu bạn đang dựa vào việc chấm dứt chương trình để dọn dẹp.


bạn có thể suy luận, vì bạn có thể thấy biến thể hiện đó là một con trỏ tới thể hiện của lớp.
Artem Barger

3
Không cần phải tự động phân bổ đơn. Trong thực tế, đây là một ý tưởng tồi vì không có cách nào để tự động phân bổ lại bằng cách sử dụng thiết kế trên. Hãy để nó rơi ra khỏi phạm vi không gọi là kẻ hủy diệt và chỉ là lười biếng.
Martin York

Bạn có thể tự động giải quyết bằng cách sử dụng chức năng atexit. Đó là những gì chúng tôi làm (không nói đó là một ý tưởng hay)
Joe

2

Có ai đề cập đến std::call_oncestd::once_flag? Hầu hết các phương pháp khác - bao gồm khóa kiểm tra kép - đều bị hỏng.

Một vấn đề lớn trong việc thực hiện mô hình singleton là khởi tạo an toàn. Cách an toàn duy nhất là bảo vệ chuỗi khởi tạo với các rào cản đồng bộ hóa. Nhưng những rào cản đó cần phải được bắt đầu một cách an toàn. std::once_flaglà cơ chế để được đảm bảo khởi tạo an toàn.


2

Chúng tôi đã đi qua chủ đề này gần đây trong lớp EECS của tôi. Nếu bạn muốn xem chi tiết các bài giảng, hãy truy cập http://umich.edu/~eecs381/lecture/IdiomsDesPattsCreational.pdf

Có hai cách mà tôi biết để tạo một lớp Singleton chính xác.

Cách thứ nhất:

Thực hiện nó tương tự như cách bạn có nó trong ví dụ của bạn. Về phần hủy diệt, "Singletons thường chịu đựng thời gian chạy chương trình; hầu hết các hệ điều hành sẽ phục hồi bộ nhớ và hầu hết các tài nguyên khác khi chương trình chấm dứt, do đó có một lập luận cho việc không lo lắng về điều này."

Tuy nhiên, đó là một thực hành tốt để làm sạch khi chấm dứt chương trình. Do đó, bạn có thể làm điều này với một lớp SingletonDestructor tĩnh và khai báo đó là một người bạn trong Singleton của bạn.

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 sẽ được tạo khi khởi động chương trình và "khi chương trình kết thúc, tất cả các đối tượng toàn cầu / tĩnh bị phá hủy bởi mã tắt thư viện thời gian chạy (được chèn bởi trình liên kết), do đó ,_destroyer sẽ bị hủy, trình hủy của nó sẽ xóa Singleton, chạy nó kẻ hủy diệt. "

Cách thứ hai

Đây được gọi là Meyers Singleton, được tạo bởi thuật sĩ C ++ Scott Meyers. Đơn giản chỉ cần định nghĩa get_instance () khác nhau. Bây giờ bạn cũng có thể thoát khỏi biến thành viên con trỏ.

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

Điều này là gọn gàng vì giá trị được trả về là do tham chiếu và bạn có thể sử dụng .cú pháp thay vì ->truy cập các biến thành viên.

"Trình biên dịch tự động xây dựng mã tạo lần đầu tiên thông qua khai báo, không phải sau đó và sau đó xóa đối tượng tĩnh khi kết thúc chương trình."

Cũng lưu ý rằng với Meyers Singleton, bạn "có thể gặp tình huống rất khó khăn nếu các đối tượng dựa vào nhau tại thời điểm chấm dứt - khi nào Singleton biến mất so với các đối tượng khác? Nhưng đối với các ứng dụng đơn giản, điều này hoạt động tốt."


1

Ngoài các cuộc thảo luận khác ở đây, có thể đáng chú ý rằng bạn có thể có tính toàn cầu, mà không giới hạn việc sử dụng trong một trường hợp. Ví dụ, hãy xem xét trường hợp tham chiếu đếm thứ gì đó ...

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

Bây giờ ở đâu đó bên trong một hàm (chẳng hạn như main) bạn có thể làm:

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

Các giới thiệu không cần lưu trữ một con trỏ trở lại tương ứng Storevì thông tin đó được cung cấp tại thời điểm biên dịch. Bạn cũng không phải lo lắng về Storethời gian tồn tại vì trình biên dịch yêu cầu nó là toàn cầu. Nếu thực sự chỉ có một ví dụ Storethì không có chi phí nào trong cách tiếp cận này; với nhiều hơn một trường hợp, trình biên dịch sẽ thông minh về việc tạo mã. Nếu cần thiết, các ItemReflớp học thậm chí có thể được thực hiện một friendsố Store(bạn bè bạn có thể đã templated!).

Nếu Storebản thân nó là một lớp templated thì mọi thứ trở nên rắc rối hơn, nhưng vẫn có thể sử dụng phương thức này, có lẽ bằng cách thực hiện một lớp trợ giúp với chữ ký sau:

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>. */ };

Giờ đây, người dùng có thể tạo một StoreWrapperloại (và thể hiện toàn cầu) cho từng Storephiên bản toàn cầu và luôn truy cập vào các cửa hàng thông qua thể hiện trình bao bọc của họ (do đó quên đi các chi tiết chính xác của các tham số mẫu cần sử dụng Store).


0

Đây là về quản lý cuộc sống đối tượng. Giả sử bạn có nhiều hơn singletons trong phần mềm của bạn. Và họ phụ thuộc vào Logger singleton. Trong quá trình hủy ứng dụng, giả sử một đối tượng singleton khác sử dụng Logger để ghi lại các bước hủy của nó. Bạn phải đảm bảo rằng Logger sẽ được dọn sạch lần cuối. Do đó, vui lòng kiểm tra bài viết này: http://www.cs.wustl.edu/~schmidt/PDF/ObjMan.pdf


0

Cách thực hiện của tôi tương tự như của Galik. Sự khác biệt là việc triển khai của tôi cho phép các con trỏ chia sẻ dọn sạch bộ nhớ được phân bổ, trái ngược với việc giữ bộ nhớ cho đến khi thoát ứng dụng và các con trỏ tĩnh được dọn sạch.

#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

Mã của bạn là chính xác, ngoại trừ việc bạn không khai báo con trỏ cá thể bên ngoài lớp . Các khai báo lớp bên trong của các biến tĩnh không được coi là khai báo trong C ++, tuy nhiên điều này được cho phép trong các ngôn ngữ khác như C # hoặc Java, v.v.

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

Bạn phải biết rằng cá thể Singleton không cần phải bị xóa bởi chúng tôi . Chúng ta cần một đối tượng duy nhất của nó trong toàn bộ chương trình, vì vậy khi kết thúc thực hiện chương trình, nó sẽ tự động được giải quyết.


-1

Bài báo được liên kết ở trên mô tả sự thiếu sót của khóa kiểm tra kép là trình biên dịch có thể phân bổ bộ nhớ cho đối tượng và đặt một con trỏ tới địa chỉ của bộ nhớ được phân bổ, trước khi hàm tạo của đối tượng được gọi. Tuy nhiên, c ++ khá dễ dàng khi sử dụng bộ cấp phát để cấp phát bộ nhớ theo cách thủ công, sau đó sử dụng lệnh gọi cấu trúc để khởi tạo bộ nhớ. Sử dụng thẩm định này, khóa kiểm tra hai lần hoạt động tốt.


2
Không may măn. Điều này đã được thảo luận rất sâu sắc bởi một số nhà phát triển C ++ tốt nhất hiện có. Khóa kiểm tra kép bị hỏng trong C ++ 03.
Martin York

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

Thí dụ:

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

    public:
        INS(CCtrl);

-1

Lớp đơn giản, Đây phải là tệp lớp tiêu đề của bạn

#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

Truy cập singleton của bạn như thế này:

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

-3

Tôi nghĩ Bạn nên viết một hàm tĩnh trong đó đối tượng tĩnh của bạn bị xóa. Bạn nên gọi chức năng này khi bạn sắp đóng ứng dụng của mình. Điều này sẽ đảm bảo bạn không bị rò rỉ bộ nhớ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.