Jak rozumieć wskaźnik na instancję tej samej klasy?

0
#include <iostream>
#include <memory>

using namespace std;

class MyClass
{
public:
    MyClass(){
        cout << "Konstruktor" << endl;
    }

    void setFunction(MyClass *ptr){
        parent=ptr;
    }

    MyClass *function(){
        return parent;
    }

private:
    MyClass *parent=nullptr;
};

int main()
{
    MyClass *cl = new MyClass;

    cout << cl << endl;
    cl->setFunction(cl);
    cout << cl->function() << endl;

    return 0;
}

Czy ktoś wyjaśniłby mi ten kod co ja tutaj zrobiłem ? Czy mam rozumieć to tak, że zrobiłem wskaźnik który może zaadresować obiekt pokazujący na "sam siebie" ?

1

To zwykły wskaźnik, o co chodzi?

1

W nowym C++ powinieneś sprawdzić smart pointery - spróbuj zrobić kod z nimi

0
kq napisał(a):

To zwykły wskaźnik, o co chodzi?

chodzi o to, że zrobiłem klasę MyClass, a w tej klasie jest funkcja która przyjmuje również wskaźnik MyClass

void setFunction(MyClass *ptr)

więc jeżeli tak, to jak mam to rozumieć ? że jakby adresowała sama siebie ?

3

Masz dwa wskaźniki które patrzą na obiekt MyClass.

3

MyClass *cl = new MyClass; tworzy obiekt klasy MyClass, który nie ma rodzica (jest null_ptr). Następnie wywołujesz cl->setFunction(cl) (choć to powinno się nazywać setParent) i to w rzeczy samej ustawia rodzica obiektu cl na samego siebie. Logicznie trochę bez sensu, przydałby się tam test if (this == ptr), żeby wyeliminować takie zachowanie, czyli rzucić wyjątkiem, zwrócić kod błędu, etc.

3

Co do wątku, ciezko "zrozumieć" jak tyle lat trenowało się umyusł na "jestem guru i przyjmowac wiedzy nie potrzebuję"
To sie nazywa wdrukowanie. Kiepsko widzę

zkubinski napisał(a):

, a w tej klasie jest funkcja która przyjmuje również wskaźnik MyClass

void setFunction(MyClass *ptr)

więc jeżeli tak, to jak mam to rozumieć ? że jakby adresowała sama siebie ?

Mam w neuronach ułożone rozróżnienie klasa a obiekt klasy / instancja ? Chyba nie, jesli widzisz w tym "samą siebie"

metodę ze wskaźnikiem na inny obiekt tej samej klasy można zaakceptować jak "integracyjną"
"zrób coś, co zintegruje stan mój z tamtym", np operator dodawania w językach, które nie mają nazywanych (forsowanych) operatorów

I zarazem, wracając do C/C++, tu jest duży problem.
We wszystkich racjonalnych użyciach tego wzorca jak by dopuszczał jedynie
void setFunction(const MyClass *ptr)
aby wyrazić "tego drugiego obiektu nie zmieniam"

0
jvoytech napisał(a):

MyClass *cl = new MyClass; tworzy obiekt klasy MyClass, który nie ma rodzica (jest null_ptr). Następnie wywołujesz cl->setFunction(cl) (choć to powinno się nazywać setParent) i to w rzeczy samej ustawia rodzica obiektu cl na samego siebie. Logicznie trochę bez sensu, przydałby się tam test if (this == ptr), żeby wyeliminować takie zachowanie, czyli rzucić wyjątkiem, zwrócić kod błędu, etc.

Twoja odpowiedź dla mnie jest najbardziej sensowna. Chciałbym zapytać dlaczego uważasz, że wskazywanie na samego siebie jest bez sensu ? Bo ja to rozumiem tak:

  1. Powołuję do życia obiekt MyClass *cl = new MyClass; który nie ma nazwy ale został nadany mu adres i zawiera wszystkie składowe tej klasy. Jedną z tych składowych jest funkcja,
void setFunction(MyClass *ptr)
{
  parent=ptr;
}

która przyjmuje wskaźnik na obiekty tej samej klasy. Czyli Jak zostanie powołany do życia ten obiekt wraz ze składowymi tej klasy, to funkcja przyjmująca argument obiektu MyClass wykona jakby kopię samej siebie bez adresu i jak ustawię tam adres to ten adres będzie wskazywał na samego siebie - bo inaczej jak to rozumieć ?

  1. Użyłeś fajnego określenia rodzic czyli ten obiekt mogę wykorzystać do wskazania obiektom potomnym kto jest ich rodzicem - czy dobrze pomyślę, że to bardzo może się przydać przy dziedziczeniu ? Bo obiekty potomne, to te które dziedziczą po klasie np abstrakcyjnej ?
2

@zkubinski:

Rodzica się dostaje jakiś czas po urodzeniu ? Kurczę, muszę zweryfikowac swoja wiedzę z biologii
(hint: rodzica się przypisuje w kosntruktorze)

zkubinski napisał(a):

Bo obiekty potomne, to te które dziedziczą po klasie np abstrakcyjnej ?

Wiesz co jest najgorsze? Pozór wiedzy. Jest o wiele, wiele gorsza od zupełnej niewiedzy.

Nie podejmuję się odpowiedzieć szczegółowo na kolejne zdania. Nie jaram tego co ty.

3
zkubinski napisał(a):
  1. Powołuję do życia obiekt MyClass *cl = new MyClass; który nie ma nazwy ale został nadany mu adres i zawiera wszystkie składowe tej klasy. Jedną z tych składowych jest funkcja,
void setFunction(MyClass *ptr)
{
  parent=ptr;
}

która przyjmuje wskaźnik na obiekty tej samej klasy. Czyli Jak zostanie powołany do życia ten obiekt wraz ze składowymi tej klasy, to funkcja przyjmująca argument obiektu MyClass wykona jakby kopię samej siebie bez adresu i jak ustawię tam adres to ten adres będzie wskazywał na samego siebie - bo inaczej jak to rozumieć ?

Instancja klasy (obiekt) nie zawiera funkcji składowych. W momencie tworzenia obiektu NIE TWORZĄ się kopie funkcji składowych!

Funkcje składowe dostają jako niejawny argument wywołania wskaźnik na obiekt dla kórego zostały wywołane.

Pisane bez sprawdzania więc mogą być błędy:

class Foo {
public:
// publiczne tylko w celach demonstracyjnych !
  int value;   
  
  void set_value(int v);
};

void set_value(Foo *obj, int v)
{
  obj->value = v;
}

void Foo::set_value(int v)
{
  value = v;
  
  // w tym kontekście jest to równoważne
  this->value = v;

  // this jest wskaźnikiem na obiekt, dla którego wywołano funkcję składową
}


int main()
{
  Foo f;
  f.set_value(10);
  set_value(&f, 20);
}
2
zkubinski napisał(a):

Twoja odpowiedź dla mnie jest najbardziej sensowna. Chciałbym zapytać dlaczego uważasz, że wskazywanie na samego siebie jest bez sensu ?

MyClass brzmi zbyt abstrakcyjnie, ale gdyby nazywała się konkretniej np. Person to nielogiczne byłoby ustawienie pola parent danej instancji na samego siebie, tylko lepiej zostawić jako null co oznaczałby rodzic nieznany. Sprawdzenie przy nadawaniu rodzica czy ptr to jest to samo co this byłoby wskazane. W innym zastosowania pewnie nie miałoby to większego znaczenia.

  1. Powołuję do życia obiekt MyClass *cl = new MyClass; który nie ma nazwy ale został nadany mu adres i zawiera wszystkie składowe tej klasy. Jedną z tych składowych jest funkcja,
void setFunction(MyClass *ptr)
{
  parent=ptr;
}

która przyjmuje wskaźnik na obiekty tej samej klasy. Czyli Jak zostanie powołany do życia ten obiekt wraz ze składowymi tej klasy, to funkcja przyjmująca argument obiektu MyClass wykona jakby kopię samej siebie bez adresu i jak ustawię tam adres to ten adres będzie wskazywał na samego siebie - bo inaczej jak to rozumieć ?

Nie, nie wykona kopi samej siebie bo przekazujesz tylko adres obiektu już istniejącego, instancji stworzonej później albo własny adres jak to jest w Twoim przypadku. Oczywiście ten adres jest niepewny bo jak tamten obiekt zostanie zwolniony to pole parent instancji cl będzie wskazywała w miejsce w którym będą "śmieci".

  1. Użyłeś fajnego określenia rodzic czyli ten obiekt mogę wykorzystać do wskazania obiektom potomnym kto jest ich rodzicem - czy dobrze pomyślę, że to bardzo może się przydać przy dziedziczeniu ? Bo obiekty potomne, to te które dziedziczą po klasie np abstrakcyjnej ?

rodzic w sensie węzeł nadrzędny/poprzedni w liście jedno-dwu kierunkowej albo w drzewie. Nie chodziło mi o dziedziczenie w klasach, ale gdyby to był C a nie C++ to dziedziczenie pewnie dałoby się jakoś zaimplementować.

0
jvoytech napisał(a):

Nie, nie wykona kopi samej siebie bo przekazujesz tylko adres obiektu już istniejącego, instancji stworzonej później albo własny adres jak to jest w Twoim przypadku. Oczywiście ten adres jest niepewny bo jak tamten obiekt zostanie zwolniony to pole parent instancji cl będzie wskazywała w miejsce w którym będą "śmieci".

czyli żeby nie pokazywał na "śmieci" i aby się przed tym ustrzec, to powinienem zrobić sprawdzenie o którym wspomniałeś tj if(this==ptr) czyli

MyClass *parent(){

  if(this==ptrParent)
  {
    return this;
  }
  
  ptrParent=nullptr;  
  return ptrParent;
}
1

sprawdzanie w tej funkcji to trochę za późno. O takie coś mi chodziło:

    void setParent(MyClass *ptr){
        if (this == ptrParent) {
            throw std::invalid_argument("you can't be your own parent!");
        }
        ptrParent=ptr;
    }

    MyClass *parent(){
        return ptrParent;
    }
3

Czy ktoś wyjaśniłby mi ten kod co ja tutaj zrobiłem ?

Utworzyłeś jednokierunkową listę cykliczną złożoną z jednego elementu.

2

Przy tak trywialnym przykładzie i problemie bardzo ciężko mi zrozumieć, czego nie rozumiesz.

Czy poniższy kod jest dla ciebie niezrozumiały? (zachowałem bezsensowne nazewnictwo z oryginału)

class MyClass
{
public:
    MyClass(){
        cout << "Konstruktor" << endl;
    }

    void setFunction(int ptr){
        parent=ptr;
    }

    int function(){
        return parent;
    }

private:
    int parent=nullptr;
};

A może tego nie rozumiesz?

struct ListNode
{
    void setNext(ListNode* ln) { next_ = ln; }
    ListNode* next() const { return next_; }
private:
    int value;
    ListNode* next_;
};

Co do rozróżniania klasy od obiektu, wg mnie przydatną analogią jest plan/budynek. Klasą jest BlokZWielkiejPłyty, a jej instancjami (obiektami) są rozsiane po całym bloku wschodnim straszaki.

2

@ZrobieDobrze: niekoniecznie musi być const. zobacz jak patentów ustawia qt one nie są const. W ogóle z tego tematu wychodzi inna sprawa gość używa qt który bazuje na relacji rodzic-dziecko i po co i dlaczego opisano w dokumentacji. Czyli wyszło by na to że przepisywał kod z skrawków z netu bez zrozumienia.

https://doc.qt.io/qt-6/objecttrees.html

0

dobra, dlaczego o to pytam ?
Bo analizuję sobie przykład modelu w Qt i jest tam taka ciekawostka którą próbuję zrozumieć i dlatego założyłem ten temat

plik treemodel.cpp

TreeModel::TreeModel(const QString &data, QObject *parent)
    : QAbstractItemModel(parent)
{
    rootItem = new TreeItem({tr("Title"), tr("Summary")});
    qDebug()<< "TreeModel rootItem" << rootItem;
    setupModelData(data.split('\n'), rootItem); //analiza tej funkcji doprowadziła mnie do tego 
    //czemu założyłem ten wątek i tutaj przekazywany jest wskaźnik JAKBY "do samego siebie"
}
void TreeModel::setupModelData(const QStringList &lines, TreeItem *parent) //tutaj przyjmuję obiekt typu TreeItem
{
    QList<TreeItem *> parents;
    QList<int> indentations;
    parents << parent; //tutaj przypisuje się ten sam wskaźnik, a w zasadzie jego "rodzic"
    indentations << 0;

    int number = 0;

    while (number < lines.count()) {
        int position = 0;
        while (position < lines[number].length()) {
            if (lines[number].at(position) != ' ')
                break;
            position++;
        }

        const QString lineData = lines[number].mid(position).trimmed();

        if (!lineData.isEmpty()) {
            // Read the column data from the rest of the line.
            const QStringList columnStrings =
                lineData.split(QLatin1Char('\t'), Qt::SkipEmptyParts);
            QList<QVariant> columnData;
            columnData.reserve(columnStrings.count());
            for (const QString &columnString : columnStrings)
                columnData << columnString;

            if (position > indentations.last()) {
                // The last child of the current parent is now the new parent
                // unless the current parent has no children.

                if (parents.last()->childCount() > 0) {
                    parents << parents.last()->child(parents.last()->childCount()-1);
                    indentations << position;
                }
            } else {
                while (position < indentations.last() && parents.count() > 0) {
                    parents.pop_back();
                    indentations.pop_back();
                }
            }
            qDebug()<< "parents last" << parents.last();
            // Append a new item to the current parent's list of children.
            parents.last()->appendChild(new TreeItem(columnData, parents.last())); //drugi parametr konstrukotra i ten sam wskaźnik jest przekazywany
        }
        ++number;
    }
}

i dochodzimy do konstruktora obiektu TreeItem

plik treeitem.cpp

TreeItem::TreeItem(const QList<QVariant> &data, TreeItem *parent)
    : m_itemData(data), m_parentItem(parent) //wskaźnik m_parentItem który został przekazany w powyższym kodzie
{}

a klasa wygląda tak
plik treeitem.h

class TreeItem
{
public:
   
    TreeItem *parentItem();

private:
    QList<TreeItem *> m_childItems;
    QList<QVariant> m_itemData;
    TreeItem *m_parentItem; //i właśnie o te cudo mi chodzi i dlatego założyłem wątek
};

I dlatego zrobiłem sobie taki "pseudokod" aby zrozumieć co tu się dzieje i dlaczego w tym przykładzie zastosowali taki trik

0

@zkubinski: zacznij od struktur, od listy jednokierunkowej.

#include <iostream>

using namespace std;


struct wezel{
  
wezel *nastepny;  
int nr_wezla;  
    
};


int main()
{
// tworzymy wezel poczatkowy do ktorego zawsze bedziemy mogli wrocic
wezel *poczatek; 
poczatek=new wezel;    
    
wezel *tmp;
// ustawiamy na poczatku
tmp=poczatek;

// tworzymy nowy wezwl na kazdym wezel->nastepny , potem przesuwamy tam wskaznik
for(int i=0;i<3;i++){
                    tmp->nastepny=new wezel;
                    tmp=tmp->nastepny;  
                    tmp->nr_wezla=i;}


// ustawiamy na pierwszym wezle po poczatku
tmp=poczatek->nastepny;

// wyswietlamy wszystkie wezly idac jeden po drugim
for(int i=0;i<3;i++){

                    cout<<"numer wezla   "<<tmp->nr_wezla<<"\n";
                    tmp=tmp->nastepny;}     

    return 0;
}

Nie dodałem delet'a, trzeba by dać jeszcze jedną pętle do usuwania wszystkiego.

3
johnny_Be_good napisał(a):

@zkubinski: zacznij od struktur,



struct wezel{
  
wezel *nastepny;  
int nr_wezla;  
    
};


int main()
{
// tworzymy wezel poczatkowy do ktorego zawsze bedziemy mogli wrocic
wezel *poczatek; 
poczatek=new wezel;    
    
wezel *tmp;
// ustawiamy na poczatku
tmp=poczatek;

Po pierwsze masz zerowe rozumienie czym jest struct w C++. To takie samo class tylko default public
Po drugie proponowanie czegoś w C++ co jest dynamicznie powoływane i nie kontrolowane konstruktorem, to patologia.

Budujcie sobie kącik wzajemnej adoracji, jednakowo dobrze obaj na tym wyjdziecie.
@zkubinski 'emu jest wszytsko jedno, nawet anty-wzorzec kodu łyknie

3

Jak rozumieć wskaźnik na instancję tej samej klasy?

Dokładnie tak samo, jak wskaźnik na instancję innej klasy.

Problem, jak tego się nie rozumie / się nie zinternalizowało tej wiedzy.
Wątek jest daremny.

0

@zkubinski Przyczaj się na ten kurs Complete Modern C++ (C++11/14/17) dzisiaj cena jest zaporowa 349,99 PLN
normalnie to coś kolo 50 PLN

0


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