Czemu od C++11 dopiero zmienna static w funkcji jest thread safe?

1

Witam

Mam pytanie odnośnie singletona Meiera:

class Singleton final
{
    public:
   ~Singleton() = default;
    static Singleton& instance()
    {
      static Singleton inst;
      return inst;
    }

   private:
     Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton &)=delete;

}
w main:
Singleton& obiekt=Sigleton::instance();

Czy to C++11 czy wcześniejszy to przecież zmienna static jest umieszczana w data/bss segmencie. A więc nawet jak jeszcze nie wejdziemy w ciało funkcji przy uruchomieniu programu to instancja singletona już istnieje. Więc przed C++11 też powinna być thread-safe.

0

Czy to C++11 czy wcześniejszy to przecież zmienna static jest umieszczana w data/bss segmencie. A więc nawet jak jeszcze nie wejdziemy w ciało funkcji przy uruchomieniu programu to instancja singletona już istnieje. Więc przed C++11 też powinna być thread-safe.

Nie jestem przekonany, ale nawet jeśli tak, to czy nie jest to szczegół implementacyjny, którego standard wcześniej nie wymuszał?

PS. Jak =delete to lepiej public:. :)

1

Obawiam się, że przed C++11 standard nie definiował zachowania wielowątkowego, więc formalnie było to undefined behavior, ale kompilatory zapewniały w tym wypadku thread safty.
Może jakiś bardziej uważny czytelnik standardu rzuci odpowiednim cytatem.

2

BTW przed C++11 szybki i thread safe singleton wyglądał tak:

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

private:
    Singleton() {}
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton &);

private:
    volatile static Singleton *inst;
}

/// in cpp:
Singleton *Singleton::inst = NULL;
static Lock instLock;

Singleton& instance() {
    if (!inst) {
        SyncLock lock(instLock);
        if (!inst) {
             Singleton *tmp = new Singleton();
             inst = tmp;
        }
    }
    return *inst;
}

Pisane z pamięci więc może być coś przekręcone, a nie mogę znaleźć artykułu naukowego opisującego czemu to wygląda jak wygląda.
Najbardziej jest zakręcona cześć ze zmienną tmp, trzeba być świadomym jakie cuda potrafi zrobić kompilator podczas optymalizacji.

Poza tym jestem zagorzałym wrogiem singletonów i popieram stwierdzenie, że jest to antypattern. Jedyne singletony jakie mają sens to: Application i Logger (no i może jeszcze cuda z gtest), cała reszta jest po prostu przejawem lenistwa i głupoty programistów.

2

Sekcja 6.7 standardu:

such a variable is initialized the first time control passes through its declaration; such a variable is considered initialized upon the completion of its initialization. [...] If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

Stopka...

The implementation must not introduce any deadlock around execution of the initializer.

Ale pomijając już dyskusję o sensowności Singletona (bo już i tak wiele powiedziano i mi się... nie chce strzępić klawiatury... )

Nie lepiej CRTP?

#include <iostream>
template<class T> class Singleton {
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);
protected:
	Singleton() {}
	virtual ~Singleton() {}
public:
	static T& getInstance() {
		static T _instance;
		return _instance;
	}
};

class IsSingleton: public Singleton<IsSingleton> {
	int value;
protected:
	friend class Singleton<IsSingleton>;
	IsSingleton() { value = 42; }
public:
	int getValue() const { return value; }
	void setValue(int a) {
		value = a;
	}
};

Pokazuje intencję.

1 użytkowników online, w tym zalogowanych: 0, gości: 1