Forum: PC-Programmierung C++ lokale static variable in Member


von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

Hallo,

ich habe sehr lange (fast 20 Jahre :-)) nicht mehr in c++ programmiert 
und bin im Moment dabei zu wiederholen und aufzuholen, weil ich nächstes 
Jahr ein größeres Projekt angehen möchte.

Folgendes Problem ist vermutlich trivial, erschließt sich mir aktuell 
aber nicht. Je nachdem wo die Variable "static int64 mseconds" definiert 
wird, funktioniert der Code oder auch nicht.

Funktionieren heißt, bei jedem Aufruf der class member function 
"::buttonClicked" wird der Wert von mseconds aktualisiert. Nicht 
funktionieren heißt, beim ersten Aufruf übernimmt die Variable einen 
korrekten Wert, wird später aber nicht aktualisiert.

Der Code benutzt Library Funktionen, die sollten aber nicht stören, weil 
ich vermute, dass das Problem darin liegt, dass mir die Bedeutung von 
static nicht vollständig klar ist und nicht in Nebenwirkungen der 
Library.

Ich würde das Problem gern verstehen, kann mir jemand einen 
sachdienlichen Hinweis geben?



Der Code:

funktioniert nicht:
1
void MainContentComponent::buttonClicked(Button* button)
2
{
3
    
4
    if (button == &checkTheTimeButton)                                                      
5
    {
6
        const Time currentTime (Time::getCurrentTime());                                    
7
        static int64 mseconds = currentTime.toMilliseconds();
8
        const String currentTimeString (mseconds);
9
        
10
        timeLabel.setText (currentTimeString, dontSendNotification);
11
    }
12
}

funktioniert:
1
void MainContentComponent::buttonClicked(Button* button)
2
{
3
    static int64 mseconds;
4
    if (button == &checkTheTimeButton)                                                      
5
    {
6
        const Time currentTime (Time::getCurrentTime());                                    
7
        mseconds = currentTime.toMilliseconds();
8
        const String currentTimeString (mseconds);
9
        
10
        timeLabel.setText (currentTimeString, dontSendNotification);
11
    }
12
}




vlg
 Timm


Edit: Jaja, man braucht hier kein static, das ist aber eine andere 
Frage. Ich möchte in der nächsten Iteration gern die Zeit des letzten 
Aufrufs wissen ...

: Bearbeitet durch User
von Carl D. (jcw2)


Lesenswert?

Die statische Variable wird eben im ersten Beispiel nur einmal 
initialisiert. Die lokale im zweiten jedesmal.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Timm R. schrieb:
> Ich würde das Problem gern verstehen, kann mir jemand einen
> sachdienlichen Hinweis geben?

http://en.cppreference.com/w/cpp/language/storage_duration

Und da einfach mal den Absatz über Static local variables lesen. In 
Deinem "geht nicht"-Beispiel wird die Variable nur einmal mit dem 
Funktionsaufruf initialisiert, im anderen Beispiel initialisiert (mit 0) 
und dann immer wieder zugewiesen. Falsch (unangebracht) ist das `static` 
in beiden Beispielen.

von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

Hallo,

Carl D. schrieb:
> Die statische Variable wird eben im ersten Beispiel nur einmal
> initialisiert. Die lokale im zweiten jedesmal.

ach herrje, wie simpel. Ist ja eigentlich klar, wenns mans weiß :-)

Herzlichen Dank!

Timm

von Tom (Gast)


Lesenswert?

Für alle mitlesenden nur zur Sicherheit: Die static-Variable existiert 
einmal für die ganze Klasse, nicht pro Objekt, wie man vielleicht 
vermuten könnte. Wenn es also mehrere MainContentComponent-Objekte gibt, 
sollte einem dies bewusst sein. Wenn man die static-Variable tief in 
einer unübersichtlichen Funktion versteckt, kann man seinen Nachfolgern 
lustige Fallen stellen:
1
#include <iostream>
2
#include <string>
3
4
class Knopf
5
{
6
    std::string name;
7
public:
8
    void drueck() {
9
        static size_t aufrufe = 0;
10
        ++aufrufe;
11
        std::cout << "Knopf " << name << " wurde " << aufrufe << " mal gedrückt.\n";
12
    }
13
    Knopf (const char* s) : name(s) {
14
    }
15
};
16
17
int main()
18
{
19
    Knopf ein("Ein");
20
    ein.drueck();
21
    ein.drueck();
22
    // Jahre spaeter nachgeruestet, der Geraet hat einen Ausschalter bekommen:
23
    Knopf aus("Aus");   
24
    aus.drueck();
25
}

von nfet (Gast)


Lesenswert?

Danke für die Info.
Nicht das ich jemals auf die Idee gekommen wäre eine static Variable in 
einer Methode zu definieren, aber das Ergebnis überrascht mich doch.
Hätte intuitiv auch erwartet, dass es die Variable für jedes Objekt 
einmal gibt.
Wenn man aber noch mal genauer darüber nachdenkt, ist es recht 
konsequent gelöst. Eine static variable wird genau dann das erste Mal 
gesetzt, wenn der Code dazu das erste mal ausgeführt wird (jaja, es sei 
denn sie ist 0, dann darf der Compiler das auch schon davor).

Man kann sich auch wenn man C++ fast täglich verwendet, immer wieder 
überraschen lassen :D

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

nfet schrieb:
> Man kann sich auch wenn man C++ fast täglich verwendet, immer wieder
> überraschen lassen :D

In dem Fall des OP handelt es sich um Funktions lokale, statische 
Variablen und die gibt es auch schon in C. Hat also überhaupt nichts mit 
Klassen oder Objekten zu tun.

von Dr. Sommer (Gast)


Lesenswert?

nfet schrieb:
> Hätte intuitiv auch erwartet, dass es die Variable für jedes Objekt
> einmal gibt.

Das kann nicht so sein, denn: Nachdem der Compiler die "class" 
Deklaration einer Klasse eingelesen hat, muss er die Größe des Objekts 
im Speicher wissen, sodass man Instanzen davon anlegen kann. Die 
Funktions-Bodys können ja auf beliebige weitere Source-Dateien 
(Translation Units) verteilt sein, und daher müsste der Compiler erst 
alle Dateien durchgehen um die Größe eines Objekts zu bestimmen.

von nfet (Gast)


Lesenswert?

Torsten R. schrieb:
> In dem Fall des OP handelt es sich um Funktions lokale, statische
> Variablen und die gibt es auch schon in C. Hat also überhaupt nichts mit
> Klassen oder Objekten zu tun.

Naja, es ist eben gerade keine "Funktions" lokale, statische Variable, 
sondern eine "Methoden" lokale, statische Variable.

Dr. Sommer schrieb:
> Das kann nicht so sein, denn: Nachdem der Compiler die "class"
> Deklaration einer Klasse eingelesen hat, muss er die Größe des Objekts
> im Speicher wissen, sodass man Instanzen davon anlegen kann. Die
> Funktions-Bodys können ja auf beliebige weitere Source-Dateien
> (Translation Units) verteilt sein, und daher müsste der Compiler erst
> alle Dateien durchgehen um die Größe eines Objekts zu bestimmen.

Das stimmt natürlich, daran hatte ich gar nicht gedacht. Danke für den 
Nachtrag.
Das ist dann auch ein weiterer Grund, warum Templates in der Deklaration 
auch implementiert werden müssen.
1
#include <iostream>
2
#include <string>
3
4
template <int templateparameter> class Knopf
5
{
6
    std::string name;
7
public:
8
    void drueck() {
9
        static size_t aufrufe = 0;
10
        ++aufrufe;
11
        std::cout << "Knopf " << name << " wurde " << aufrufe << " mal gedrückt.\n";
12
    }
13
    Knopf (const char* s) : name(s) {
14
    }
15
};
16
17
int main()
18
{
19
    Knopf<1> ein("Ein");
20
    ein.drueck();
21
    ein.drueck();
22
    // Jahre spaeter nachgeruestet, der Geraet hat einen Ausschalter bekommen:
23
    Knopf<2> aus("Aus");   
24
    aus.drueck();
25
}
Ausgabe:
1
Knopf Ein wurde 1 mal gedrückt.
2
Knopf Ein wurde 2 mal gedrückt.
3
Knopf Aus wurde 1 mal gedrückt.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

nfet schrieb:
> Torsten R. schrieb:
>> In dem Fall des OP handelt es sich um Funktions lokale, statische
>> Variablen und die gibt es auch schon in C. Hat also überhaupt nichts mit
>> Klassen oder Objekten zu tun.
>
> Naja, es ist eben gerade keine "Funktions" lokale, statische Variable,
> sondern eine "Methoden" lokale, statische Variable.

C++ kennt aber keine Methods, sondern nur functions (free functions and 
member functions). Für das obige Beispiel gelten die Regeln für 
functions.

> Das ist dann auch ein weiterer Grund, warum Templates in der Deklaration
> auch implementiert werden müssen.

Müssen Sie doch überhaupt nicht. Es gibt auch eine Möglichkeit eine 
member function eines class templates getrennt zu definieren:
1
template <int templateparameter>
2
void Knopf< templateparameter >::drueck() {
3
}

von Dr. Sommer (Gast)


Lesenswert?

Torsten R. schrieb:
> Es gibt auch eine Möglichkeit eine member function eines class templates
> getrennt zu definieren:

Ja, aber dennoch bevor sie aufgerufen wird...

von Wilhelm M. (wimalopaan)


Lesenswert?

Tom schrieb:
> Für alle mitlesenden nur zur Sicherheit: Die static-Variable existiert
> einmal für die ganze Klasse, nicht pro Objekt

Man kann (freien / Element-)Funktionen einen Zustand geben (wie hier), 
man kann Objekten einen Zustand geben, man kann Klassen einen Zustand 
geben, man kann Übersetzungseinheiten einen Zustand geben, man kann 
einen programmglobalen Zustand haben.

Hier ist die Modellierung natürlich von Anfang an falsch, denn die 
Anzahl der Tastendrücke sollte ein Objektzustand sein.

von Wilhelm M. (wimalopaan)


Lesenswert?

nfet schrieb:

>
1
> 
2
> template <int templateparameter> class Knopf
3
> {
4
> ...
5
> 
6
> int main()
7
> {
8
>     Knopf<1> ein("Ein");
9
>     ein.drueck();
10
>     ein.drueck();
11
>     // Jahre spaeter nachgeruestet, der Geraet hat einen Ausschalter 
12
> bekommen:
13
>     Knopf<2> aus("Aus");
14
>     aus.drueck();
15
> }
16
>

Unterschiedlich parametrierte Templates sind unterschiedliche Typen. 
Hier ist es so, als hättest Du zwei Klassen KnopfA und KnopfB definiert.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Dr. Sommer schrieb:
> Torsten R. schrieb:
>> Es gibt auch eine Möglichkeit eine member function eines class templates
>> getrennt zu definieren:
>
> Ja, aber dennoch bevor sie aufgerufen wird...

Ich verstehe nicht ganz was Du meinst aber: man kann ein template auch 
explizit instanziieren, dann muss die Definition der Funktion nicht in 
der aktuellen Übersetzungseinheit bekannt sein.

von Dr. Sommer (Gast)


Lesenswert?

Torsten R. schrieb:
>
> Ich verstehe nicht ganz was Du meinst aber: man kann ein template auch
> explizit instanziieren, dann muss die Definition der Funktion nicht in
> der aktuellen Übersetzungseinheit bekannt sein.
Du meinst wahrscheinlich eine Template-Spezialisierung; ja da geht das, 
aber das ist auch kein "richtiges" template ;-) Und vorher deklarieren 
muss man es auch.

von Rolf M. (rmagnus)


Lesenswert?

Dr. Sommer schrieb:
> Torsten R. schrieb:
>>
>> Ich verstehe nicht ganz was Du meinst aber: man kann ein template auch
>> explizit instanziieren, dann muss die Definition der Funktion nicht in
>> der aktuellen Übersetzungseinheit bekannt sein.
> Du meinst wahrscheinlich eine Template-Spezialisierung;

Ob er das meint, weiß ich nicht. Gehen müßte es aber bei beiden.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Dr. Sommer schrieb:
>> Torsten R. schrieb:
>>>
>>> Ich verstehe nicht ganz was Du meinst aber: man kann ein template auch
>>> explizit instanziieren, dann muss die Definition der Funktion nicht in
>>> der aktuellen Übersetzungseinheit bekannt sein.
>> Du meinst wahrscheinlich eine Template-Spezialisierung;
>
> Ob er das meint, weiß ich nicht. Gehen müßte es aber bei beiden.

Funktionstemplates kann man explizit und implizit instanziieren.

Klassentemplates muss man explizit instanziieren, sofern man keine class 
template deduction guides (C++17) verwendet.

Ist dem Compiler die Definition des templates bekannt, weil sie bspw. in 
derselben(!) Übersetzungseinheit enthalten ist, so kann er das template 
vollständig instanziieren. Ist ihm nur die Deklaration des templates 
bekannt, so baut er ganz normal eine unaufgelöste Referenz ein und 
verlässt sich darauf, dass die Definition an anderer Stelle ist und 
durch den Linker aufgelöst werden kann. Wenn die Definition sich in 
einer anderen Übersetzungseinheit befindet, so muss in dieser 
Übersetzungseinheit die vollständige (explizite) Instanziierung 
vorgenommen werden: sprich, der Programmierer muss wissen, welche 
unterschiedlichen Instanziierungen im gesamten Programm vorkommen und 
diese hier explizit machen. Das ganze ist also machbar, aber eigentlich 
Quatsch (eine richtige Möglichkeit, templates extern zu definieren, 
wurde nie in C++ aufgeommen).

Deswegen ist der "normale" Ansatz, die vollständige Definition des 
Templates in einer(!) Header-Datei vorzunehmen. Ob man hier die 
Deklaration einer Template-Elementfunktion von ihrer Definition trennt, 
ist technisch unerheblich. Praktisch ist es bei umfangreicheren 
Klassentemplates.

Templates führen grundsätzlich nicht zu ODR Problemen, da mehrfache 
gleiche Instanziierungen in unterschiedlichen Übersetzungseinheiten 
durch den Linker entfernt werden (ähnlich inline).

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Dr. Sommer schrieb:

> Du meinst wahrscheinlich eine Template-Spezialisierung; ja da geht das,
> aber das ist auch kein "richtiges" template ;-) Und vorher deklarieren
> muss man es auch.

Nein, ich meine "Explicit instantiation" 
(http://en.cppreference.com/w/cpp/language/class_template). Meine 
Antwort bezog sich ja auf:

>> Ja, aber dennoch bevor sie aufgerufen wird...

Welches ich als implizite Instanzierung interpretiert habe. Aber wie 
gesagt: ich konnte den Kommentar eh nicht so richtig einordnen ;-)

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> Das ganze ist also machbar, aber eigentlich
> Quatsch (eine richtige Möglichkeit, templates extern zu definieren,
> wurde nie in C++ aufgeommen).

Doch, aufgenommen wurde so eine Möglichkeit in C++98. Sie wurde nur nie 
signifikant von den Compilern unterstützt und ist deshalb inzwischen 
wieder aus C++ entfernt worden.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Wilhelm M. schrieb:
> sprich, der Programmierer muss wissen, welche
> unterschiedlichen Instanziierungen im gesamten Programm vorkommen und
> diese hier explizit machen. Das ganze ist also machbar, aber eigentlich
> Quatsch ...

Für den (eher seltenen) Fall, dass die Menge der nötigen 
Instanziierungen bekannt sind, würde ich explizite Instanziierung aber 
immer bevorzugen, vor allem, wenn es komplexere Templates sind.

von Wilhelm M. (wimalopaan)


Lesenswert?

Torsten R. schrieb:
> Wilhelm M. schrieb:
>> sprich, der Programmierer muss wissen, welche
>> unterschiedlichen Instanziierungen im gesamten Programm vorkommen und
>> diese hier explizit machen. Das ganze ist also machbar, aber eigentlich
>> Quatsch ...
>
> Für den (eher seltenen) Fall, dass die Menge der nötigen
> Instanziierungen bekannt sind, würde ich explizite Instanziierung aber
> immer bevorzugen, vor allem, wenn es komplexere Templates sind.

Und wozu?

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Wilhelm M. schrieb:
>> Das ganze ist also machbar, aber eigentlich
>> Quatsch (eine richtige Möglichkeit, templates extern zu definieren,
>> wurde nie in C++ aufgeommen).
>
> Doch, aufgenommen wurde so eine Möglichkeit in C++98. Sie wurde nur nie
> signifikant von den Compilern unterstützt und ist deshalb inzwischen
> wieder aus C++ entfernt worden.

Was ich damit ja sagen wollte.
 export" ist seit C++11 wieder draussen.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Wilhelm M. schrieb:
>> Für den (eher seltenen) Fall, dass die Menge der nötigen
>> Instanziierungen bekannt sind, würde ich explizite Instanziierung aber
>> immer bevorzugen, vor allem, wenn es komplexere Templates sind.
>
> Und wozu?

Kürzere Compile-Zeiten. Es geht ja nicht nur darum, dass der Compiler 
ständig die gleichen Templates instanziieren muss. Wenn ich die 
Implementierung in einer eigenen Übersetzungseinheit "verstecken" kann, 
dann spare ich dem Compiler ja auch noch die ganzen header, die nur für 
die Implementierung benötigt werden.

Der Fall kommt leider nicht sehr häufig vor, aber kommt vor. (z.B. in 
der Nähe von Visitor-Patterns).

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Torsten R. schrieb:
> Wilhelm M. schrieb:
>>> Für den (eher seltenen) Fall, dass die Menge der nötigen
>>> Instanziierungen bekannt sind, würde ich explizite Instanziierung aber
>>> immer bevorzugen, vor allem, wenn es komplexere Templates sind.
>>
>> Und wozu?
>
> Kürzere Compile-Zeiten.

Das sollten die Compiler eigentlich selbst können mit 
precompiled-header.

> Es geht ja nicht nur darum, dass der Compiler
> ständig die gleichen Templates instanziieren muss. Wenn ich die
> Implementierung in einer eigenen Übersetzungseinheit "verstecken" kann,
> dann spare ich dem Compiler ja auch noch die ganzen header, die nur für
> die Implementierung benötigt werden.

Das für mich ausschlaggebende Argument wäre das "Verstecken" der 
Implementierung, so dass ein Anwender nicht in die Versuchung gerät, 
"gegen" die Implementierung eine Anwendung zu schreiben (das war die 
Motivation von "export template").

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> Torsten R. schrieb:
>> Wilhelm M. schrieb:
>>>> Für den (eher seltenen) Fall, dass die Menge der nötigen
>>>> Instanziierungen bekannt sind, würde ich explizite Instanziierung aber
>>>> immer bevorzugen, vor allem, wenn es komplexere Templates sind.
>>>
>>> Und wozu?
>>
>> Kürzere Compile-Zeiten.
>
> Das sollten die Compiler eigentlich selbst können mit
> precompiled-header.

Naja, wenn man sehr intensiv Templates nutzt, muss für jede 
Übersetzungseinheit das halbe Programm nochmal gebaut werden, nur damit 
das dann nachher vom Linker wieder alles verworfen wird. Mit precompiled 
headers kostet das weniger Zeit, aber trotzdem kostet es noch Zeit.
Es hat ja durchaus seinen Grund, warum man ohne Templates auch nicht 
einfach alles im Header implementiert.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.