Forum: Mikrocontroller und Digitale Elektronik Zugriff von Lambda auf Attrbute in const Objekt


von Rene (Gast)


Lesenswert?

Hallo,

ich möchte von einer Klasse ein Objekt erzeugen und dieses mit 
geschweiften Klammern initialisieren.

Die Klasse beinhaltet einen Funktionszeiger, den ich auch mittels 
Lambdaausdrucks initialisieren möchte und welcher Zugriff auf die 
Atribute ha`ben soll.

Folgendes Beispiel geht leider nicht. Gibt es da eine Möglichkeit?
1
class bla{
2
  public:
3
    void (*z)();
4
    int x;
5
6
  private:
7
};
8
9
bla blub = {
10
  .z = [this](){
11
    x = 0;
12
  },
13
  .x = 0
14
};

von Klaus W. (mfgkw)


Lesenswert?

Außerhalb der Klasse kannst du kein this nehmen...

PS: die ganze Lambdafuntion ist außerhalb der Klasse, die wird keine 
Methode werden können.

Muß mal grübeln, ob das gehen könnte, was du vielleicht vorhast...

: Bearbeitet durch User
von Klaus W. (mfgkw)


Lesenswert?

Was du als z deklarierst, ist auch keine Methode, sondern nur ein Zeiger 
auf eine Funktion.

Den kannst du als Lambdafunktion setzen. Aber die kann dann nicht auf x 
zugreifen, weil sie keine Methode ist.
Und wie gesagt kann sie auch kein this nehmen - weil keine Methode.

von Rene (Gast)


Lesenswert?

Ich wollte eine Menustruktur definieren, als Konstanten im Flash. Und 
ein Parameter sollte eben sein, was bei Buttons passieren soll und das 
wollte ich als Lamda definieren.

Nun müsste ich ständig einen zeiger mitgeben und der Schreibaufwand wird 
massiv. Gibt es da eine elegantere Lösung?

So irgendwie wollte ich das halt lösen:
1
class Bildschirm{
2
  public:
3
    void (*button)();
4
    void (*print)();
5
};
6
7
Bildschirm Bild1 = {
8
  .button = [this](){};
9
  [...]
10
};
11
12
[...]

von tut nix zur Sache (Gast)


Lesenswert?

Versuch doch erstmal, deine Lambdas als normale Funktion zu formulieren 
- wenn das funktioniert, kannste immer noch ne Lambda machen.
1
class Bildschirm{
2
  public:
3
    void (*button)();
4
    void (*print)();
5
};
6
7
void onButtonPress()
8
{}
9
10
11
Bildschirm Bild1 = {
12
  .button = &onButtonPress;
13
  [...]
14
};

Was du hier versuchst zu tun, nennt sich Dependency Injection bzw. 
Inversion of Control. Und wenn der aufrufende Code dynamisch (zur 
Laufzeit) bestimmen können soll, was der aufgerufene Code zu tun hat - 
dann gehts halt nicht ohne Funktionspointer.

Wenn du das Objekt noch mitgeben willst, musste halt deine Callback 
anpassen:
1
class Bildschirm{
2
  public:
3
    void (*button)(Bildschirm* const);
4
    void (*print)(Bildschirm* const);
5
};
6
7
void onButtonPress( Bildschirm* const screen)
8
{}

Beim Zugriff auf Bild1.button musste halt Bild1 als Parameter mit 
reinschleifen.

von Rene (Gast)


Lesenswert?

tut nix zur Sache schrieb:
> (zur Laufzeit)

Ich würde das hingegen gerne als Konstante zur Compilezeit definieren 
können, so dass ich mir den Bildschirm* const sparen kann. Aber das geht 
scheinbar nicht.

von Klaus W. (mfgkw)


Lesenswert?

Da würde ich eher Funktionsobjekte nehmen.
Das sind Objekte, in deren Klasse der operator() überladen ist (ggf. mit 
Parametern bei Bedarf) und die deshalb wie eine Funktion resp. 
Funktionszeiger resp. Lambdafunktion verwendet werden können.

Der zugehörige Parameter in deiner Bildschirmklasse könnte dann ein 
std::function sein.

Der Vorteil von Funktionsobjekten ist, daß sie noch Daten enthalten 
können (wie man halt Member in ihrer Klasse definiert).

So könnte man das Funktionsobjekt, das in dem Bildschirm abgelegt wird, 
mit dem Bildschirm wiederum initialisieren. Dann weiß das Objekt, wessen 
Methoden es aufrufen muß.

von Wilhelm M. (wimalopaan)


Lesenswert?

Es sind einfach zu viele Syntaxfehler drin:
1
#include <functional>
2
3
class Bildschirm{
4
  public:
5
    const std::function<void()> button;
6
    void (* const print)();
7
};
8
int main() {
9
10
    Bildschirm Bild1 = {
11
      .button = []{},
12
      .print = [](){}
13
    };
14
}

Im wesentlichen der Trenner ";" statt "," zwischen den "designated 
initializers";

Trotzdem ist das wahrscheinlich mit der Konvertierung zu einem 
Funktionszeiger nicht das, was Du möchtest. Denn das geht nur für 
capture-less lambda-expr (aus naheliegenden Gründen).
1
#include <functional>
2
#include <iostream>
3
4
class Bildschirm{
5
  public:
6
    const std::function<void()> button;
7
    void (* const print)();
8
};
9
10
int main() {
11
    int x{};
12
    
13
    Bildschirm Bild1 = {
14
      .button = [&x]{
15
            std::cout << __PRETTY_FUNCTION__ << '\n';
16
        },
17
// nicht moeglich
18
//      .print = [&x](){
19
//            std::cout << __PRETTY_FUNCTION__ << '\n';            
20
//        }
21
    };
22
    Bild1.button();
23
    Bild1.print();
24
}

: Bearbeitet durch User
von Klaus W. (mfgkw)


Lesenswert?

Ja, sieht schon besser aus.

Aber trotzdem sind button() und print() "nur" Funktionen und keine 
Methoden. Deshalb werden sie nicht auf Interna des Bildschirms zugreifen 
können, was Rene ursprünglich mal wollte mit seinem this.

von Wilhelm M. (wimalopaan)


Lesenswert?

Ok, das hatte ich überlesen:
1
#include <functional>
2
#include <iostream>
3
4
class Bildschirm{
5
  public:
6
    const std::function<void()> button;
7
    void (* const print)();
8
    
9
    void onEvent(auto f) {
10
        f(this);
11
    };
12
};
13
14
int main() {
15
    int x{};
16
    
17
    Bildschirm Bild1 = {
18
      .button = [&x]{
19
            std::cout << __PRETTY_FUNCTION__ << '\n';
20
        },
21
//      .print = [&x](){
22
//            std::cout << __PRETTY_FUNCTION__ << '\n';            
23
//        }
24
    };
25
    Bild1.button();
26
    Bild1.print();
27
    
28
    Bild1.onEvent([](const Bildschirm* const b){
29
        
30
    });
31
    
32
}

von Wilhelm M. (wimalopaan)


Lesenswert?

Vielleicht hilf dies weiter, weil es jetzt glaube ich besser der 
Intention des OP entscpricht:
1
#include <functional>
2
#include <iostream>
3
#include <concepts> // C++20
4
5
class Bildschirm{
6
  public:
7
    void f() const {
8
        button(*this);
9
    }
10
    void g() const {
11
        std::cout << __PRETTY_FUNCTION__ << '\n';        
12
    }
13
    void onF(std::invocable<Bildschirm> auto const f) {
14
        button = f;
15
    }
16
private:
17
    std::function<void(const Bildschirm&)> button;
18
};
19
20
int main() {
21
22
    Bildschirm Bild1;
23
    
24
    Bild1.onF([](const Bildschirm& b){
25
        std::cout << __PRETTY_FUNCTION__ << '\n';        
26
        b.g();
27
    });
28
    
29
    Bild1.f();
30
}

Designated initializers sind hier fehl am Platz, sie eignen sich 
hauptsächlich für POD-like DT.

: Bearbeitet durch User
von Mombert H. (mh_mh)


Lesenswert?

Wilhelm M. schrieb:
> Ok, das hatte ich überlesen:
> #include <functional>
> #include <iostream>
> class Bildschirm{
> [...]
>    Bild1.onEvent([](const Bildschirm* const b){
>    });

Ich würde vermuten, dass das der massive Schreibaufwand ist, den er 
vermeiden möchte ;-)

Rene schrieb:
> Nun müsste ich ständig einen zeiger mitgeben und der Schreibaufwand wird
> massiv.

von Wilhelm M. (wimalopaan)


Lesenswert?

Mombert H. schrieb:
> Ich würde vermuten, dass das der massive Schreibaufwand ist, den er
> vermeiden möchte ;-)

Oh ha ;-)
Den hätte er doch sowieso (Beim Erzeugen des Closures in der LE, wenn 
dort ein Capture des (noch nicht gebauten) Objektes möglich wäre).

Mombert H. schrieb:
> Rene schrieb:
>> Nun müsste ich ständig einen zeiger mitgeben und der Schreibaufwand wird
>> massiv.

Zeiger (nullable reference) ist an der Stelle eh sehr fragwürdig. Denn 
ein nullptr an der Stelle wäre immer falsch, und man müsste expl. 
zusichern oder ein run-time check auf nullptr machen.
Daher habe ich das oben geändert.

: Bearbeitet durch User
von 🐧 DPA 🐧 (Gast)


Lesenswert?

Ich glaube der einzige Weg, einen echten this pointer zu bekommen, ist 
mit Klassen und virtuellen Methoden:
1
#include <iostream>
2
#include <array>
3
4
class MenuItem {
5
public:
6
  MenuItem(std::string name) : name(name) {}
7
  std::string name;
8
  virtual void run() const = 0;
9
  virtual void show() const = 0;
10
};
11
12
namespace menu {
13
  const class Bla : public MenuItem {
14
  public:
15
    Bla(std::string name) : MenuItem(name) {}
16
    void run() const {
17
      std::cout << "run bla bla" << std::endl;
18
    }
19
    void show() const {
20
      std::cout << "show " << this->name << std::endl;
21
    }
22
  } bla {"bla"};
23
  const class M123 : public MenuItem {
24
  public:
25
    M123(std::string name) : MenuItem(name) {}
26
    void run() const {
27
      std::cout << "run 123 " << this->name << std::endl;
28
    }
29
    void show() const {
30
      std::cout << "show " << this->name << std::endl;
31
    }
32
  } m123 {"123"};
33
  const std::array<const MenuItem*,2> menu = {
34
    &bla,
35
    &m123
36
  };
37
}
38
39
int main(){
40
  for(auto item : menu::menu)
41
    item->show();
42
}

Ich habe schon lange (ein paar Jahre) klein C++ mehr geschrieben. OMG 
ist das scheisse. Was ich da alles gelernt habe, was man in C++ nicht 
machen kann.

Sowas wie offset_of oder container_of bei c ist mit member pointern 
nicht one hacks machbar.
Kein new von anonymen Klassen mit virtuellen Methoden, und Konstruktoren 
kann man bei anonymen Klassen auch vergessen.
Im globalen Scope eine Referenz von einem Compound literal nehmen, um da 
in einem Member eines Objekts zu initialisieren, geht auch nicht, anders 
als in C ist das dort dann ne temporäre Referenz!?!
Und ein normales C array von anonymen C Klassen Instanzen als Compound 
literal zu erstellen, scheint schon rein syntaktisch nicht möglich zu 
sein!?!
Einfach mal was wie .name initialisieren alla designated initialisers, 
gibts auch nicht.
Und die std::array brauchen immer noch die Angabe, wie viele Elemente da 
sind, auch wenn man es direkt initialisiert?
Und den Bla construktor konnte es mir auch nicht automatisch generieren, 
wie das andere Sprachen wie z.B. JavaScript könnten?
WTF, wie kann mit dem Mist irgend wer arbeiten? Dieses C++ ist ja fast 
noch schlimmer als Rust!

von Wilhelm M. (wimalopaan)


Lesenswert?

🐧 DPA 🐧 schrieb:
> Ich glaube der einzige Weg, einen echten this pointer zu bekommen, ist
> mit Klassen und virtuellen Methoden:

Leider hast Du mit Deinem Post am Thema vorbei geschrieben: es geht 
nicht(!) um Laufzeitpolymorphie.

von Rene (Gast)


Lesenswert?

Ich habe evtl. eine Lösung gefunden.
1
class A {
2
  public:
3
    int x;
4
    void createConstMembers();
5
    std::function<void()> f;
6
};
7
8
A B_Test;
9
10
void A::createConstMembers(){
11
  B_Test = A{
12
    .f = [this](){
13
      x = 0;
14
    }
15
  };
16
}

von Mombert H. (mh_mh)


Lesenswert?

Rene schrieb:
> Ich habe evtl. eine Lösung gefunden.class A {
>   public:
>     int x;     void createConstMembers();     std::function<void()> f;
> };
> A B_Test;
> void A::createConstMembers(){   B_Test = A{
>     .f = [this](){       x = 0;
>     }
>   };
> }
Wofür brauchst du jetzt noch f. wenn du eh ne neue Methode schreibst?

von Wilhelm M. (wimalopaan)


Lesenswert?

Rene schrieb:
> Ich würde das hingegen gerne als Konstante zur Compilezeit definieren
> können, so dass ich mir den Bildschirm* const sparen kann. Aber das geht
> scheinbar nicht.

Ich glaube zwar nicht, dass der OP noch dabei ist, doch trotzdem: 
wieviele "Bildschirm"-Objekte hast Du denn?

Einen?
Dann macht es keinen Sinn, im Code die Möglichkeit zu haben, mehrere 
Objekte davon zu erzeugen. Es sei denn, Du baust Deine Klasse als 
Monostate.
Oder Du verwendest ein Singleton (ggf. mit anderen Problemen).

Meistens hat man es bei µC-Anwendungen nur mit einer begrenzten Anzahl 
von Objekten als Abstraktion einer (internen) Peripherie zu tun. Dann 
kannst Du auch gleich auf Heap/Stack-Objekte bzw. globale Objekte 
verzichten, und statt dessen static-only Klassen bzw. templates 
verwenden.

von Rene (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Ich glaube zwar nicht, dass der OP noch dabei ist, doch trotzdem:
> wieviele "Bildschirm"-Objekte hast Du denn?

An die zehn und jedes Objekt soll eine eigene Funktion für jeden 
Tastendruck und die "print" Funktion erhalten.

Das wäre zumindest meine Idee. In der Klasse ist fast alles static (die 
Ressourcen), lediglich die Funktionspointer sind Objektattribute.

Derzeit habe ich halt für jede Taste und die Bildschirmausgabe ein 
riesiges Switch-Case.

Mit der Lösung, die ich hier nochmal poste, kann ich scheinbar zumindest 
Objekte erstellen, die alle auf die gemeinsamen static Attribute 
zugreifen können. Ich bin da aber nicht zufrieden mit, die teilen sich 
natürlich auch potentiell alle das gleiche Objekt.
1
class A {
2
  public:
3
    static int x;
4
    void createConstMembers();
5
    std::function<void()> f;
6
};
7
8
A B_Test;
9
10
void A::createConstMembers(){
11
  B_Test = A{
12
    .f = [this](){
13
      x = 0;
14
    }
15
  };
16
}

von Mombert H. (mh_mh)


Lesenswert?

Wo ist der Vorteil gegenüber
1
class A {
2
  public:
3
    static int x;
4
    void f();
5
};
6
A B_Test;
7
void A::f(){
8
    x = 0;
9
}
?

von Rene (Gast)


Lesenswert?

Mombert H. schrieb:
> Wo ist der Vorteil gegenüberclass A {

Ich kann nicht für jedes Object eine individuelle Methode erstellen. Die 
machen alle das Selbe.

Bei meiner Lösung kann ich auch schreiben:
1
class A {
2
  public:
3
    static int x;
4
    void createConstMembers();
5
    std::function<void()> f;
6
};
7
8
A A_Test;
9
A B_Test;
10
11
void A::createConstMembers(){
12
  A_Test = A{
13
    .f = [this](){
14
      x = 0;
15
    }
16
  };
17
18
  B_Test = A{
19
    .f = [this](){
20
      x = 10;
21
    }
22
  };
23
}

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.