Forum: PC-Programmierung C++ public static variable


von fred (Gast)


Lesenswert?

Hallo zusamen,

ich kenne C und C# für mich zufriedenstellend und beschäftige mich 
gerade mit C++.

Hier habe ich Fragen zu public static vor Variablen.

Was funktioniert:
Anmerkung, SerialNrFromCMDGetNetStart ist hier noch nicht static.

Im h:------------------------------------------------------
class CSMAData
{
public:
  CSMAData();
  virtual ~CSMAData();

  uint32_t SerialNrFromCMDGetNetStart;
};




Im cpp:----------------------------------------------------
CSMAData::CSMAData()
{
  // TODO Auto-generated constructor stub

}

CSMAData::~CSMAData() {
  // TODO Auto-generated destructor stub
}




In z.B. main():-----------------------------------------------

CSMAData oCSMAData;

oCSMAData.SerialNrFromCMDGetNetStart = 12345;




Das Obige funktioniert soweit ohne das der Compiler meckert.
Nun will ich aber diese Variable "SerialNrFromCMDGetNetStart" statisch 
haben. Setze ich also ein static davor, dann meckert der Compiler.
"...undefined reference to `CSMAData::SerialNrFromCMDGetNetStart'"

Wenn ich allerdings im zugehörigen cpp file folgendes "definiere" muss 
es wohl heißen, dann gibt sich der Compiler zufrieden.



Im h:------------------------------------------------------
class CSMAData
{
public:
  CSMAData();
  virtual ~CSMAData();

  static uint32_t SerialNrFromCMDGetNetStart;
};



Im cpp:----------------------------------------------------
CSMAData::CSMAData()
{
  // TODO Auto-generated constructor stub

}

CSMAData::~CSMAData() {
  // TODO Auto-generated destructor stub
}

uint32_t CSMAData::SerialNrFromCMDGetNetStart = 0;



In main():-----------------------------------------------

CSMAData oCSMAData;

oCSMAData.SerialNrFromCMDGetNetStart = 12345;




Das scheint so zu funktionieren. Finde es nur etwas umständlich.
Diese Klasse soll eigentlich nur Daten für alle anderen Objekte 
bereitstellen.
Ist hier diese aus meiner Sicht umständliche Handhabung mit einer Klasse 
eine gute Wahl oder gibt es andere Möglichkeiten?

von Dr. Sommer (Gast)


Lesenswert?

fred schrieb:
> Das scheint so zu funktionieren. Finde es nur etwas umständlich.
So ist es aber korrekt. Speicher muss nunmal irgendwo angefordert weden.

fred schrieb:
> Ist hier diese aus meiner Sicht umständliche Handhabung mit einer Klasse
> eine gute Wahl oder gibt es andere Möglichkeiten?
Das kommt drauf an was du machen möchtest. statische Variablen in einer 
Klasse von verschiedenen Stellen im Code zu schreiben erscheint nicht 
sehr objektorientiert und ist im Endeffekt nur eine globale Variable, 
welche ja bekanntermaßen zu unübersichtlichem schlecht wartbarem Code 
führen.
Die Frage ist also: Warum ist die Variable statisch, brauchst du sicher 
nur eine Instanz davon, kannst du den Zugriff kapseln?

von Rolf M. (rmagnus)


Lesenswert?

fred schrieb:
> Das Obige funktioniert soweit ohne das der Compiler meckert.
> Nun will ich aber diese Variable "SerialNrFromCMDGetNetStart" statisch
> haben. Setze ich also ein static davor, dann meckert der Compiler.
> "...undefined reference to `CSMAData::SerialNrFromCMDGetNetStart'"

Du hast die Variable zwar deklariert, aber nicht definiert.

> Wenn ich allerdings im zugehörigen cpp file folgendes "definiere" muss
> es wohl heißen, dann gibt sich der Compiler zufrieden.

> uint32_t CSMAData::SerialNrFromCMDGetNetStart = 0;

Ja, richtig.

> Diese Klasse soll eigentlich nur Daten für alle anderen Objekte
> bereitstellen.

Was du damit sagen willst, erschießt sich mir nicht. Welche "anderen 
Objekte" meinst du?

> Ist hier diese aus meiner Sicht umständliche Handhabung mit einer Klasse
> eine gute Wahl oder gibt es andere Möglichkeiten?

Falls du damit meinst, dass die Klasse gar nicht instanziiert werden 
soll, sondern nur statische Member hat, dann würde ich keine Klasse, 
sondern einen Namespace dafür verwenden. Die Aufteilung in Deklaration 
und Definition hast du damit aber trotzdem.

von fred (Gast)


Lesenswert?

Habe etwas nachgelesen und glaube es soweit verstanden zu haben.

Bei nicht statischen Variablen werden diese automatisch definiert, wenn 
ein Objekt angelegt wird. Bei statischen Variablen ist dies nicht der 
Fall. Deswegen diese umständliche Definition und und in meinem Fall die 
Initialisierung mit 0.

Unabhängig davon mal im Ernst.... wer heute auf dem PC zu C++ greift 
wird sehr triftige und spezielle Gründe haben, sofern er sich nicht 
gerne Quält.

Ich mache was privat auf einem µC und will das mit C++ und nicht C 
machen.

von fred (Gast)


Lesenswert?

@Rolf Magnus


Namespace ... verstehe ich und kenne ich auch von C#.
Scheint aber etwas verpönt zu sein.

Ginge das auch über struct? Vermutlich schon, doch da werde ich wohl 
auch das mit der Definition haben.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Alternativ geht auch:

> In main():-----------------------------------------------

> CSMAData::SerialNrFromCMDGetNetStart = 12345;

Extra ein eigenes Objekt musst du dafür nicht anlegen.  Außerdem ist die 
Schreibweise klarer dass das Ding an der Klasse klebt, nicht an einem 
speziellen Objekt.

von Nase (Gast)


Lesenswert?

fred schrieb:
> Bei statischen Variablen ist dies nicht der
> Fall. Deswegen diese umständliche Definition und und in meinem Fall die
> Initialisierung mit 0.
Diese "umständliche Definition" hat aber wenig mit C++ zu tun. Auch in C 
musst du Definition und Deklaration trennen (Stichwort: external).

Insgesamt hat auch das wenig mit C selbst zu tun, sondern mit dem 
Linken.


> Unabhängig davon mal im Ernst.... wer heute auf dem PC zu C++ greift
> wird sehr triftige und spezielle Gründe haben, sofern er sich nicht
> gerne Quält.
Na wenn du meinst.

von Rolf M. (rmagnus)


Lesenswert?

fred schrieb:
> @Rolf Magnus
>
> Namespace ... verstehe ich und kenne ich auch von C#.

Dort geht das aber meines Wissens so nicht.

> Scheint aber etwas verpönt zu sein.

Das wäre mir neu.

> Ginge das auch über struct? Vermutlich schon, doch da werde ich wohl
> auch das mit der Definition haben.

struct und Klassen sind in C++ das gleiche, bis auf die 
default-Zugriffsrechte und die default-Vererbungsrechte (public bei 
struct, private bei Klassen).
Aber eine struct, die nur statische Member hat, halte ich für genauso 
unsinnig, wie eine Klasse, bei der das so ist.

von Dr. Sommer (Gast)


Lesenswert?

fred schrieb:
> Bei nicht statischen Variablen werden diese automatisch definiert, wenn
> ein Objekt angelegt wird. Bei statischen Variablen ist dies nicht der
> Fall.
Genau wie bei C#. Ich dachte das kannst du...

fred schrieb:
> wer heute auf dem PC zu C++ greift
> wird sehr triftige und spezielle Gründe haben, sofern er sich nicht
> gerne Quält.
Auch auf dem PC bietet C++ hohe Performance bei starker Abstraktion. 
C++'s Möglichkeiten zur Meta-Programmierung haben nur wenige andere 
Sprachen (z.B. Lisp), weshalb es durchaus Grund gibt C++ zu verwenden.

Mach dir doch mal eine ordentliche Klassenstruktur (wie in C#) und ob da 
wirklich statische Member rein müssen, oder ob das später nur zu Chaos 
führt.
Ich halte mich meistens an den Grundsatz, dass man praktisch das ganze 
Programm 2x in einem Prozess ausführen können sollte, indem man nur in 
der main() die entsprechenden Objekte doppelt anlegt. Wenn das nicht 
geht, weil da globale/statische Member -Variablen reinpfuschen, ist die 
Struktur vermutlich schlecht. Auch wenn man das Programm wahrscheinlich 
nie wirklich so "verdoppeln" würde, erzwingt diese Vorgehensweise eine 
saubere Klassen-Struktur. Randfälle gibt es bei "fixen" Daten die sich 
nie ändern aber 1x geladen werden müssen, wie Grafiken o.ä.

von fred (Gast)


Lesenswert?

Also ich habe mit freeRTOS vorerst mindestens zwei tasks am laufen.

Der eine task soll permanent über eine serielle Schnittstelle mit einem 
Gerät kommunizieren und alle x Sekunden Daten sammeln.

Der andere task soll auch auf diese gesammelten Daten zugreifen können 
und diese auf eine SD Karte speichern.

Das mit den beiden tasks ist für mich gesetzt. Das will ich so machen.
Also bitte keine Diskussion darüber. Klar, das könnte man auch alles 
ohne RTOS machen.

Daher mein Gedanke zu statischen Variablen in einer Datenklasse.
Auf diese kann aus beiden tasks zugegriffen werden.

Frage.... wie würdet ihr das prinzipiell elegant machen?

Danke und Gruß

von Dr. Sommer (Gast)


Lesenswert?

fred schrieb:
> Daher mein Gedanke zu statischen Variablen in einer Datenklasse.
> Auf diese kann aus beiden tasks zugegriffen werden.

Was spricht gegen normale nicht-statische Daten in einer "Datenklasse", 
von der du 1 Instanz in der main() anlegst, und Pointer darauf an beide 
Tasks übergibst? So ist das gleich viel sauberer.
Du könntest auch die Thread-Funktionen der Tasks als Memberfunktionen 
der Klasse ausführen, dann ist die ganze Angelegenheit in einer Klasse 
verpackt...

fred schrieb:
> Das mit den beiden tasks ist für mich gesetzt. Das will ich so machen.
> Also bitte keine Diskussion darüber.
Ich diskutiere doch so gerne: Die Kommunikation mit dem Gerät in 
Interrupts mithilfe von FSM und DMA abwickeln, und in der main()-Loop 
auf die SD-Karte schreiben...

von Fred (Gast)


Lesenswert?

@Dr. Sommer

Das ist auch eine Möglichkeit.

Leider habe ich bei freeRtos Problemchen mit C++.
Also, der freeRtos task funktion hatte ich zuerst eine "statische" 
Memberfunktion also die angedachte Threatfunktion einer Klasse 
übergeben. Hat soweit auch funktioniert.
Nur alle anderen Memberfunktionen dieser Klasse konnte ich irgendwie 
nicht mehr aus dieser dem task übergebenen funktion aufrufen.
Soviel zu verpacken der ganzen Angelegenheit in einer Klasse. Würde das 
schon gerne so machen aber geht leider nicht bei freeRtos.

Habe das so gelöst, dass ich in einem cpp file eine nicht member 
funktion habe, welche ich der freeRtos taskfunktion übergebe.
Diese Taskfunktion beinhaltet die endlosschleife for(;;).
Vor dieser endlosschleife habe ich alle instanzen, die ich in diesem 
prozess benötige.

Das klappt. Einen anderen Weg kenne ich leider nicht.

Haste ne Idee wie das doch funktionieren könnte mit der Klassenmethode 
als Threadfunktion?

von Carl D. (jcw2)


Lesenswert?

Für einen C++-Wrapper braucht man in irgend einer Form den this* auf die 
entsprechende Instanz der (Applikation-)Klasse. Man erzeugt dazu die 
Task mit einer statischen Methode der Klasse als TaskFunction und 
übergibt ihr den this* als pvParameters. Dann ruft man über den 
(gecasteten)this* die Member-Function-Run() der Klasse.

von Wilhelm M. (wimalopaan)


Lesenswert?

Fred schrieb:

> Leider habe ich bei freeRtos Problemchen mit C++.
> Also, der freeRtos task funktion hatte ich zuerst eine "statische"
> Memberfunktion also die angedachte Threatfunktion einer Klasse
> übergeben. Hat soweit auch funktioniert.
> Nur alle anderen Memberfunktionen dieser Klasse konnte ich irgendwie
> nicht mehr aus dieser dem task übergebenen funktion aufrufen.

Wieso nicht? (Natürlich kannst Du von einer statischen Elementfunktion 
nur wieder statische Elementfunktionen aufrufen ...)

von Fred (Gast)


Lesenswert?

@carl drexler

Hallo,
An diese Variante hatte ich auch mal gedacht.
Momentan übergebe ich über diesen pvParameters nur das QueueHandel.
Spricht aber wohl nichts dagegen einen Zeiger auf eine Struktur (Objekt) 
zu übergeben.
Diese Struktur enthält Den Zeiger auf die Klasseninstanz und das 
QueueHandel.

In dieser angedeuteten Klasse habe ich dann vermutlich eine (statische?) 
Methode die meine endlos for(;;) Thread Schleife enthält.

(Habe ich da nicht wieder das Problem das aus einer statischen Methode 
nur auf statische Methoden und Variablen zugegriffen werden kann?
Demzufolge müsste alles in der Klasse statisch sein?)

Werde ich demnächst mal ausprobieren ob das für mich die passendere 
Möglichkeit ist.
Gibt es noch andere?

von Dr. Sommer (Gast)


Lesenswert?

Fred schrieb:
> Diese Struktur enthält Den Zeiger auf die Klasseninstanz und das
> QueueHandel.
Zu kompliziert...

Fred schrieb:
> In dieser angedeuteten Klasse habe ich dann vermutlich eine (statische?)
> Methode die meine endlos for(;;) Thread Schleife enthält.
Na eben nicht statisch.

Also:
Du machst dir eine Klasse, nennen wir sie "Task". Du übergibst ihr im 
Konstruktor das QueueHandle, die Klasse merkt sich die Referenz. Du 
verpasst der Klasse eine statische Member-Funktion, welche die 
Thread-Funktion sein wird. Als pvParameters übergibst du einen Pointer 
auf die "Task" Instanz, und die statische Funktion ruft die 
nicht-statische auf:
1
class Task {
2
  public:
3
    Task (QueueHandle& qh) : m_bar(0), m_queue (qh) {}
4
    static void threadFunction (void* pvParameters) {
5
      static_cast<Task*> (pvParameters)->threadFunction ();
6
    }
7
  private:
8
    void threadFunction () {
9
      // Hier die eigentliche Thread-Arbeit durchführen.
10
      // Auf m_queue kann zugegriffen werden.
11
      while (1) {
12
        foo ();
13
      }
14
    }
15
    // Hier beliebige weitere Funktionen & Daten für die interne Behandlung unterbringen
16
    void foo() {
17
      ++m_bar;
18
    }
19
    int m_bar;
20
21
    QueueHandle& m_queue;
22
};
23
24
Task myTask;
25
int main () {
26
  // ...
27
  xTaskCreate (&Task::threadFunction, "asdf", 42, &myTask, 0, nullptr);
28
}

In der Klasse kannst du dann beliebige weitere Funktionen und interne 
Thread-Daten unterbringen, hier angedeutet durch m_bar und foo(). Ich 
habe hier die "myTask" Instanz mal global angelegt, s.d. sie keinen 
Stack-Speicher belegt, und somit die Member-Variablen (m_bar) ebenfalls 
nicht. Das kann man aber auch ändern, solange die Instanz nicht 
verschwindet während der Thread noch läuft.

PS: Hätte FreeRTOS ein C++ kompatibles Threading-API (so wie das 
C++-eigene API std::thread), könne man das so machen (rein 
hypothetisch):
1
class Task {
2
  public:
3
    Task (QueueHandle& qh) : m_bar(0), m_queue (qh) {}
4
    void threadFunction () {
5
      // Hier die eigentliche Thread-Arbeit durchführen.
6
      // Auf m_queue kann zugegriffen werden.
7
      while (1) {
8
        foo ();
9
      }
10
    }
11
  private:
12
    // Hier beliebige weitere Funktionen & Daten für die interne Behandlung unterbringen
13
    void foo() {
14
      ++m_bar;
15
    }
16
    int m_bar;
17
18
    QueueHandle& m_queue;
19
};
20
21
Task myTask;
22
int main () {
23
  // ...
24
  xTaskCreate (&Task::threadFunction, "asdf", 42, [&]() {myTask.threadFunction (); }, 0, nullptr);
25
  
26
  // Alternativ
27
28
  xTaskCreate (&Task::threadFunction, "asdf", 42, std::bind(&Task::threadFunction, &myTask), 0, nullptr);
29
30
}
Find ich schöner, aber geht halt mit so Gammel-C-Zeugs nicht ;-)

von Dr. Sommer (Gast)


Lesenswert?

Oh, mir fällt doch eine etwas abgekürzte, funktionierende Variante ein:
1
class Task {
2
  public:
3
    Task (QueueHandle& qh) : m_bar(0), m_queue (qh) {}
4
    void threadFunction () {
5
      // Hier die eigentliche Thread-Arbeit durchführen.
6
      // Auf m_queue kann zugegriffen werden.
7
      while (1) {
8
        foo ();
9
      }
10
    }
11
  private:
12
    // Hier beliebige weitere Funktionen & Daten für die interne Behandlung unterbringen
13
    void foo() {
14
      ++m_bar;
15
    }
16
    int m_bar;
17
18
    QueueHandle& m_queue;
19
};
20
21
Task myTask;
22
int main () {
23
  // ...
24
  xTaskCreate ([](void* p) {static_cast<Thread*> (p)->threadFunction (); }, "asdf", 42, &myTask, 0, nullptr);
25
}

Das funktioniert, weil Lambdas ohne Capture in Funktions-Pointer 
konvertiert werden können... Somit hält man das fiese C-API von FreeRTOS 
von der "Task" Klasse fern.

von Fred (Gast)


Lesenswert?

Dr. Sommer,

Ich verneige mich.
Danke für die tolle ausführliche Darstellung.


Eine Frage noch, dann ist die Sache für mich klar.

Die Klassendeklaration hat man doch eigentlich im Header und die 
Definition und Implementierung im *.cpp File.

Ist das was du dargestellt hast im cpp oder im h file oder ein 
zusammengeschrubsel von beidem das nur dem Verständnis dienen soll?

Die Initialisierungslisten sind doch im cpp file?

Kannste bei Zeiten vielleicht noch eine Darstellung hier angeben, die in 
etwa zeigt, was richtigerweise im h und was im cpp file zu sein hat.

Bisschen was verstehe ich schon von c++. Bin aber meilenweit von 
entfernt einen guten Stil zu haben, den ich mir aber aneignen wollte.

Also, sei bitte so freundlich.
Danke!

von Dr. Sommer (Gast)


Lesenswert?

Fred schrieb:
> Die Klassendeklaration hat man doch eigentlich im Header und die
> Definition und Implementierung im *.cpp File.

Ja ist richtig. Man kann das so machen wie gezeigt, dann sind alle 
Funktionen inline. Das ist leichter zu lesen zu Demonstrationszwecken 
aber verschwendet natürlich Programmspeicher; daher solltest du das 
umbauen wie gewohnt, mit Funktions Definitionen im Source File (inkl. 
Initialisierung). Ich kanns grad am Handy nicht machen, ist doch ne 
nette Übung für dich :P

von fred (Gast)


Lesenswert?

Hi Dr. Sommer

static_cast<Task*> (pvParameters)->threadFunction ();

oder

((Task*)(pvParameters))->threadFunction ();


Es geht ja beides sollte unteres vermieden werden? Warum?

von Dr. Sommer (Gast)


Lesenswert?

fred schrieb:
> Es geht ja beides sollte unteres vermieden werden? Warum?
Ja in dem Fall ist beides das Gleiche. Die C-Cast-Variante kann viele 
verschiedene Arten von Casts durchführen je nachdem welche Typen 
vorliegen, und es passiert leicht dass etwas anderes durchgeführt wird 
als man eigentlich wollte.
Mit static_cast macht man explizit klar was man will, und wenn das nicht 
zu den Typen passt gibt's eine Fehlermeldung anstatt dass unbemerkt 
etwas ungewolltes passiert.

Siehe auch:
https://stackoverflow.com/questions/1609163/what-is-the-difference-between-static-cast-and-c-style-casting

Beitrag #5070363 wurde von einem Moderator gelöscht.
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.