Inicjalizacja pol statycznych

0

Jak można elegancko zainicjalizować zmienne klasowe (czyli globalne dla wszystkich obiektów danej klasy) gdy zmienna ta jest np tablicą? I wo dodatku elementy tej tablicy są obliczane są za pomocą nie-trywialnego algorytmu (czyli nie np samymi zerami, ale coś bardziej skomplikowanego)

Przykład:

class A {
    static int b =13;
    static int c[666];
}

O ile gdy zmienna jest pojedyncza (b), to wtedy zaraz po jej nazwie możemy ją zainicjalizować.
Jak jednak to zrobić w przypadku zmiennej tablicowej (c)? Oczywiście mogę dodać metodę statyczną która zainicjalizuje mi tą tablicę, ale jak zrobić by metoda ta była wykonana automatycznie, na starcie programu?


Chyba źle wytłumaczyłem.

Pytam się w jaki sposób inicjalizuje się zmienne globalne klasy, które są np tablicami! Tzn w którym miejscu wrzucić kod który będzie inicjalizował daną tablicę. Tak, żeby wykonywał się on automatycznie po starcie programu... hmm nie mam dzisiaj weny pisarskiej :/

0

Musisz wplątać w to konstruktor. Są dwa sposoby: albo zastosować domyślny konstruktor elementu tablicy (stosując inne pola statyczne dla klasy elementów tablicy można to zrobić), albo stworzyć konstruktor tablicy.
Ponieważ mowa jest o tablicach intów metoda 1 odpada, aby zastosować metodę 2 trzeba zrezygnować ze zwykłej tablicy i zastosować kontener (stworzyć go samemu lub zastosować vector). np:

#include <vector.h>

class TMojaTabInt : public vector<int>
{
public:
    TMojaTabInt(int size):vector<int>(size)
    {
         // tu inicjujesz zawartość tablicy np
         at(0) = 3;
         for(int i=1;i<size;i++)
              at(i) = at(i-1)/4+(i-80)/4;
    }   
};

class A {
    static int b =13;
    static TMojaTabInt c(666); // poprawka twojej 
}

Cała reszt twojego kodu powinna pozostać bez zmian (taka miła właściwość tego kontenera :) ). Jedyna sytuacja kiedy nie zadziała to od kopa to, gdy w twoim programie pojawiają się wskaźniki do elementów tej tablicy.

0
DaQRT napisał(a)

static int b =13;

tak w ogole to to juz samo w sobie jest niepoprawne, inicjalizowac w definicji klasy mozna jedynie const statici

MarekR22: niestety, to co proponujesz nie ruszy. po drobnych poprawkach:

-sh-3.00# cat y.cpp;g++ y.cpp;
#include <iostream>
#include <vector>

class TMojaTabInt : public std::vector<int>
{
public:
    TMojaTabInt(int size) : std::vector<int>(size)
    {
         // tu inicjujesz zawartość tablicy np
         at(0) = 3;
         for(int i=1;i<size;i++)
              at(i) = at(i-1)/4+(i-80)/4;
    }
};

class A {
public:
    static int b =13;    //<-- o tym pisalem powyzej
    static TMojaTabInt c(666); // poprawka twojej   //<---tu jest blad
};

int main()
{   std::cout << A::c[5] << std::endl;
}

wynik kompilacji:

-sh-3.00# g++ y.cpp
y.cpp:18: error: ISO C++ forbids in-class initialization of non-const static
member b' y.cpp:19: error: invalid data member initialization y.cpp:19: error: (use =' to initialize static data members)

Twoj blad jest banalny: nie masz prawa w ten sposob "wywolac" konstruktora.

poprawny kod (przy okazji wciagnalem definicje klasy do srodka, zeby nie smiecic symbolami w namespace'ie:

#include <iostream>
#include <vector>

class A {
public:
    struct _c : std::vector<int>
    {
        _c() : std::vector<int>(666)    //tutaj podajesz rozmiar 'tablicy'
        {
             at(0) = 3;
             for(int i=1;i<size();i++)
                  at(i) = at(i-1)/4+(i-80)/4;
        }
    } static c;
};

A::_c A::c; //wymagana loklaizaja pola statycznego
//przy okazji sideeffect: inicjalizacja poprzez konstruktor. juz bez parametrow
//one sa wyryte w kodzie klasy na sztywno, tak jak by to bylo z rozmiarem tablicy

int main()
{
    std::cout << A::c[5] << std::endl;
}

a wracajac do oryginalnego problemu - jak zainicjalizowac konkretnie TABLICE, nie vector:

#include <iostream>

class A {
public:
    static int b;
    static int c[666];
private:
    struct _si  //'emulator' statycznego konstruktora, wystarczy jeden per klasa
    {   _si()
        {
            b = 10;
            c[0] = 3;
            for(int i=1;i<666;i++)
                c[i] = c[i-1]/4+(i-80)/4;
        }
    } static _init;
};

int A::c[]; //wymagana lokalizacja pola statycznego, bez wymiarow, one sa wyryte w definicji klasy
int A::b; //kazde pole statyczne bedace non-const wymaga lokalizacji
A::_si A::_init; //a wiec - inicjalizator rowniez!

int main()
{
    std::cout << A::c[5] << std::endl;
}

ten sposob pozwala na zainicjalizowanie wszystkich statykow w klasie w dowolny sposob i bez pisania nowych, specyficznych typow dla kazdego statica (np. pieciu subklas vector<int> bo mamy piec roznych tablic do zainicjalizowania)

uwagi:

  1. takie statyczne inicjalizatory jak zaproponowalem na koncu maja jedna "wade": zajmuja jeden bajt. koszt jest per-klasa nie per-pole, wiec majac 10 klas z taka inicjalizacja i 100 pol statycznych w kazdej z tych klas, tracisz 10 bajtow. dzieje sie tak poniewaz KAZDE pole klasy musi byc jednoznacznie adresowalne. struktura _c nie ma wlasnej zawartosci, teoretycznie wazy wiec 0B. jednakze, jako ze istnieje pole, to musi miec ono wlasny adres, wiec podczas kompilacji struktura ta uzyskuje rozmiar najmniejszy mozliwy -- czyli 1 bajt. a ze pole samo w sobie jest static.. wiec ten jeden bajt jest 'zajety' na caly czas dzialania procesu

  2. rozwiazanie wczesniejsze, z implementacja wlasnego subtypu zmiennej (tak jak u Marka albo w moim przykladzie z struct _c) ma tu teoretyczna przewage - nie ma tego ukrytego kosztu. jest natomiast.. koszt klasy vector. na g++ sizeof(vector<int>) to 12 bajtow. placony per-klasa-i-pole. majac 10 klas i 100 tablic statycznych w kazdej z nich, tracisz 1210100 bajtow (nie mowiac o tym, ze dla kazdej musisz napisac odpowiedni typ danych)

polecam wiec rozwiazanie ostatnie. "moje", moze wyglada troche brzydziej, ale sa same plusy. i pozwala inicjalizowac/wyliczac np. double, czego rozwiazaniem poprzednim raczej sie nie zrobi szybko (pisac typ class Double..?)

0

Dzięki za pomoc quetzalcoatl!

Właśnie o coś takiego mi chodziło. To, że klasa zabiera dodatkowo 1 bajt nie ma dla mnie znaczenia. Trzeba przyznać, że sam problem nie jest taki oczywisty do rozwiązania od razu...

0

quetzalcoatl, tak z ciekawości pytam, bo ja osobiście mam uraz do takich klas-zagnieżdżajek, i robię to troszkę inaczej:
http://4programmers.net/Forum/433708?h=#id433886
czy to jakieś zasadnicze wady ma? bo tak na oko, to narzut chyba ten sam... [???]

0

narzut teoretycznie u mnie mniejszy, praktycznie - ten sam

u Ciebie:
= jedna zmienna statyczna bool (1B) w klasie Klasa
= jedna metoda statyczna w klasie Klasa

u mnie:
= jedna zmienna statyczna wielkosci 0B (praktycznie 1B) typu 'anonimowego'
= jeden konstruktor dla tego typu 'anonimowego'

roznice sa kosmetyczno-codebeauty'owe:

u Ciebie:
= wymaga lokalizacji w/w zmiennej

bool Klasa::flaki_ustawione [...] ;
  • wymaga inicjalizacji w/w zmiennej
bool Klasa::flaki_ustawione = Klasa::poustawiaj();

u mnie:
= wymaga lokalizacji w/w zmiennej

A::_ustawiacz A::ustawiacz;
  • inicjalizacja jest z automatu

u Ciebie:

  • jesli zapomnisz o inicjalizacji, nie dostaniesz bledu kompilacji, najwyzej blad runtime'a, o ile gdzies napiszesz sobie sprawdzanie flagi "flaki_ustawione"

u mnie:

  • nie da sie zapomniec o inicjalizacji. co najwyzej o lokalizacj - ale wtedy bedzie blad kompilacji/linkowania.

u Ciebie:
= do Klasy dodajesz metode i pole
[g++ warning (bez wall bez ansi bez pedantic): all member functions in class Klasa are private]

u mnie:
= do Klasy dodaje pole i jego typ

u Ciebie:
= jak sie uprzec, mozna te sama funkcje inicjalizacyjna uzywac dla roznych klas

u mnie:
= jak sie uprzec, mozna te sam typ inicjalizatora uzywac dla roznych klas

ps. hmm.. mozna by z tego zrobic arta "Nietrywialna inicjalizacja pol statycznych". Niech no znajde godzinke:)

0

u Ciebie:

  • jesli zapomnisz o inicjalizacji, nie dostaniesz bledu kompilacji, najwyzej blad runtime'a, o ile gdzies napiszesz sobie sprawdzanie flagi "flaki_ustawione"

u mnie:

  • nie da sie zapomniec o inicjalizacji. co najwyzej o lokalizacj - ale wtedy bedzie blad kompilacji/linkowania.

aj waj, ty rację masz jak cholera. nigdy mi się nie zdarzyło dotąd, ale zdarzyć się może! przechodzimy na twoje klasy!

0

Mam problem gdy przerabiam klasę A na szablonową, np:

template<int N> class A { ... }
Nie wiem jak zlokalizować ten ostatni statyczny składnik (_si...)
Próbowałem rzeczy w stylu:

template<int N> A<N>::_si A<N>::_init;
ALBO template<int N> A<N>::_si A::_init;
ALBO template<int N> template<N> A<N>::_si A<N>::_init;
ALBO template<int N> template<N> A<N>::_si A::_init;
ALBO template<int N> A<N>::_si template<N> A<N>::_init;

, ale każda z nich kończyła się błędem kompilacji :( Gdy nic nie wspominałem kompilatorowi o tym składniku - program uruchamiał się, ale inicjalizacja globalna nie była wykonywana.

Wiem, że z szablonami nawet najlepsze kompilatory mają problemy. Chciałbym to jednak, mniej lub bardziej elegancko, rozwiązać. Używam Microsoft Visual C++ 2008.

0

a co to za _init?
Jeśli mówisz o statycznym składniku _si to oczekiwałbym, że jego symbol pojawi się on jako ostatni w definicji, a ja tu widzę na końcu jakieś _init (i to po ::).
Dajże nieco więcej kodu, musimy zobaczyć co to jest _si i to _init.

0

Proszę, oto cały program (lekko przerobione to co wysłał quetzalcoatl parę postów wyżej):

template<int N>
class A {
public:
    static int b;
    static int c[666];
    A() { printf("Local initialize\n"); }
private:
    struct _si
    {   _si()
        {
            printf("Global initialize\n");
            b = 10;
            c[0] = 9;
            for(int i=1;i<666;i++)
                c[i] = c[i-1]/4+(i-80)/4;
        }
    } static _init;
};

template<int N> int A<N>::c[];
template<int N> int A<N>::b;
template<int N> A<N>::_si A<N>::_init; // <--------------------- ZA TĄ LINIJKA MAM PROBLEMY. JAK TO ZAPISAĆ?

int main()
{
	A<5> a, b, c, d;
	printf("%d\n", A<5>::c[5]);
	return 0;
}
0

No, ale ze mnie ślepy sklerotyk ;) .
Problem najwyraźniej wynika z próby stworzenia zmiennej o nieokreślonym typie.
Typ innych pól statycznych można ustalić przy kompilacji bez konkretyzacji typu wyjściowego, natomiast to tragiczne pole _init typu _si zależy od konkretyzacji szablonu i dlatego to nie chce się skompilować (przy określeniu typu dla tego przypadku masz <N>).
Najprostszym rozwiązaniem jest wyciągnięcie typu _si poza klasę A, a do tej klasy dołożyć przyjaźń z typem _si.
Doczytam jeszcze swoją ściągę czy nie ma jakiegoś innego bardziej przejrzystego sposobu.

--uzupełnienie--
Na razie wydaje mi się, że są tylko dwa sposoby:

  1. Jak wyżej, ale po przetestowaniu wyszło na to, że przyjaźń nie pomaga, więc trzeba dołożyć do konstruktora parametry wskazujące, jakie zmienna mają być inicjalizowane.
  2. Dziedziczenie. W klasie bazowej niech pojawią się potrzebne pola statyczne i ich inicjacja, a szablon powinien rozszerzać tą klasę. Myślę, że to rozwiązanie jest najbardziej przejrzyste i błędooporne.
0

Marku.. nie kombinuj

JEDYNYM rozwiazaniem ze staticami w TEMPLATE'ach jest zadbanie aby konkretyzacja szablonu ISTNIALA

int A<5>::c[];
int A<5>::b;
int A<5>::_si A<5>::_init;

inaczej sie NIE DA. chyba, ze kompilator obsluguje keyword EXPORT, ale szczerze to troche bym odradzal

0

Jakie kombinuj?
Jeśli pola statyczne nie zależą od parametrów szablonu, to dziedziczenie jest poprawnym działającym i zrozumiałym rozwiązaniem. (należy oczywiście pamiętać, że dla każdej konkretyzacji pola statyczne są zawsze te same).

Jeśli natomiast pola statyczne zależą od parametrów szablonu to każda konkretyzacja wymaga osobnej ręcznej definicji pola statycznego (tak jak napisałeś), co oznacza, że jeśli ma być to biblioteka to bardzo niewygodna.

Zresztą jak już pisać definicję pól statycznych dla każdej konkretyzacji osobno to już bez "template<>". //q: point taken, fixed

A jeszcze jedno i to bardzo istotne. Osobnik, który wyskoczy z tym szablonem nie napisał co właściwe ma robić parametr szablonu, a szczególnie czy pola statyczne zależą od tego parametru!

0

Dziękuję wszystkim za odpowiedzi.

Czyli wystarczy samo "template<>"... Teraz program się kompiluje, ale nie działa dokładnie tak jakbym chciał (moja wina, nie napisałem czego chcę dokładnie).

na razie "konstruktor globalny" jest wykonywany tylko raz, nawet gdy utworzę kilka klas z różnymi argumentami specjalizacji. Według mnie są to różne klasy, ponieważ pola statyczne są w nich powielone.
Chciałbym żeby taki konstruktor był wykonywany dla każdej specjalizacji klasy.

Tzn mając np:

	A<5> a, b, c, d;
	A<7> e;

W pamięci istnieją tak jakby dwie klasy. I chciałbym żeby "konstruktor globalny" był wykonany dwa razy (raz dla A<5> i drugi dla A<7>).

Nie chcę tez namnażać nowych klas i dziedziczeń itd.

W między czasie przyszło mi do głowy inne rozwiązanie - które (dla moich potrzeb) działa bardzo dobrze i chyba je zastosuję. Aczkolwiek nie jest to stricto to o co pytałem - ponieważ konstruktor globalny jest wykonywany dopiero w czasie wykonywania pierwszego konstruktora danej klasy.
Mój kod:

template<int N>
class A {
public:
	static bool initialized;
	static int b[666];
	int c;

	A()
	{
		if (!initialized) staticInitialize();
		localInitialize();
	}
	void staticInitialize()
	{
		printf("staticInitialize(%d)\n", N);
		for (int i = 0; i < 666; i++)
			b[i] = (N * i * 666666) % 11111;
		initialized = true;
	}
	void localInitialize()
	{
		printf("localInitialize(%d)\n", N);
		c = N;
	}
};

template<int N> int A<N>::b[];
template<int N> bool A<N>::initialized = false;

int main()
{
	A<5> a, b, c, d;
	A<7> e;
	return 0;
}

I wyjście jakie uzyskałem:

staticInitialize(5)
localInitialize(5)
localInitialize(5)
localInitialize(5)
localInitialize(5)
staticInitialize(7)
localInitialize(7)

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