Obiekt z klasy szablonowej nie widzi funkcji C++

Witam, mam problem z klasą szablonową z którą mam problem ponieważ utworzony obiekt nie widzi funkcji składowych tej klasy.
Wywala jedynie błąd
In function ZN7Magazyn12dodajKurieraE6Kurier
undefined reference to Lista::DodajNaKoncu(Kurier*)

Lista.h

template <class T>
class Lista
{
public:
    void UsunZKonca();
    Lista<T>(T* = nullptr);
    void DodajNaKoncu(T*);
};

Lista.cpp

template <class T>
void Lista<T>::DodajNaKoncu(T* naKoniec)
{
    //Różne rzeczy
}


Magazyn.h

#include "Lista.h"
#include "Kurier.h"

using namespace std;

class Magazyn
{
    static Lista<Kurier> listaKurierow;

public:
    static void dodajKuriera(Kurier);

};

Magazyn.cpp

#include "../include/Magazyn.h"

void Magazyn::dodajKuriera(Kurier kurierDodawany)
{
    listaKurierow.DodajNaKoncu(&kurierDodawany);
}

Wiecie co może być tego przyczyną?

@synaptyk W pliku nagłówkowym Lista.h brak deklaracji funkcji Lista:DodajNaKoncu();

Jestem przekonany, że to jest największy problem w tym temacie, drugim w kolejności jest Twój pomocny komentarz.

Nie będę Ci mówił co masz robić bo to wolny kraj i jeszcze wolny internet. Moderatorom też nie zamierzam zawracać głowy pierdołami. Dla mnie to nie było śmieszne - ok są wątki, że inaczej nie można podejść do tematu i autora, ale to nie miejsca na to. Po prostu uważam, że tego typu komentarz był całkowicie zbędny, zwłaszcza, że w żadnym stopniu nie jest związany z problemem jakiego dotyczyć miał temat. Dlatego postanowiłem Ci zwrócić uwagę na to, a co z tym zrobisz to już mi obojętne. :wink:

2 polubienia

Oczywiście miałem już deklarację ale skracając te klasy przypadkowo usunąłem tę linijkę.

Dopiero teraz kojarzę, że prawdopodobnie chodzi o utworzenie, jeszcze nie istniejącego, obiektu statycznego w Magazyn.cpp ale dodanie takiej linijki wywala prawie identyczny błąd

Lista<Kurier> Magazyn::listaKurierow;

 In function _static_initialization_and_destruction_0
 undefined reference to Lista<Kurier>::Lista(Kurier*)

Daj cały kod tego szablonu i pokaż wszystkie miejsca w których w jakikolwiek sposób się do niego odwołujesz (jak nie chcesz pokazać całego kodu)

Trochę pozmieniałem nazwy, magazyn pozostaje taki jak jest
https://pastebin.com/0zrY6fV0

Mistrzem C++ nie jestem, dużo doświadczenia w nim też nie mam, ale lubię ten język. Pomimo tego, że składnia często potrafi być bardzo nieczytelna, ale Ty do tego dodałeś jeszcze strasznie pogmatwany kod.
Czemu masz listę w liście? Nie dało się nazwać tego wszystkiego w bardziej czytelny sposób, momentami gubię się i nie wiem co jest do czego i w ogóle której klasy kod czytam. Czemu nie korzystasz z kontenerów dostępnych w std, przecież tam jest lista? - jeśli program do szkoły nie było pytania.

Wydaje mi się, że znalazłem jeden błąd. Jednak nie mam pojęcia czy jest on jakkolwiek związany z Twoim komunikatem błędu.
Konstruktor Lista:
this->k = this->o;
po pierwsze o to argument konstruktora więc bez this, ale pewnie kompilator to ogarnął sam. Ważne jest to czego wskaźnik przypisujesz do k - czyli obiektu typu T. Później używasz go jak obiektu typu InnaL<T>, w funkcji DodajNaKoncu:

InnaL<T>* sK = k;

//...

k = sK->PNL();

Albo mocno czegoś nie ogarnąłem w kodzie, albo popełniłeś tu spory błąd. Dziwne tylko jest to, że kompilator w ogóle dopuścił do przypisania wskaźnika na inny obiekt - wydaje mi się, że powinien wywalić błąd. Aczkolwiek w przypadku polimorfizmu taki zabieg musi być dopuszczony, więc nie jestem pewny.

Projekt jest na uczelnię i pomyślałem sobie że może taka własna klasa będzie po prostu ładnie wyglądała i trochę przyuczę się szablonów.

Co do this to racja, przecież o jest wczytywany w konstruktorze

Ogólnie pozmieniałem nazwy by nie można było w łatwy sposób skojarzyć mojego kodu z moim kodem z internetu.
Tutaj mała ściąga
k-koniec
p-początek
d-dlugosc
a-aktualny
l-lista

Dziwne że kompilator nie zgłosił niezgodności typów przypisując InnaL<T>* adres obiektu T. Oczywiście że zamiast ‘o’ powinien tam być ‘p’ czyli początek

Lista to lista zarządzająca posiadająca adres początku i końca listy z możliwością dodawania elementów
InnaL to lista posiadająca adres poprzedniego i następnego elementu i własną wartością.

Zdaje mi się że tak powinna wyglądać lista dwukierunkowa ale mogę się mylić

Nawet usuwając cały środek ze wszystkich funkcji dalej wyskakuje błąd więc nie może to być problem z ciałem żadnej z funkcji

Spoko rozumiem :wink: wiem jak dziwni potrafią być prowadzący i że są w stanie googlować kod :smiley:.

Podrzuć mi na priv cały kod, będzie łatwiej.

Generalnie lista dwukierunkowa nie koniecznie musi posiadać ostatni element, musisz mieć wskaźnik na pierwszy, aby nie zgubić listy. Dodatkowo każdy element listy powinien być opakowany w specjalny kontener. Taki kontener powinien mieć wskaźnik na obiekt który przechowuje, oraz na element następny (lista jedno kierunkowa). W przypadku listy dwukierunkowej dodatkowo wskaźnik na element poprzedni. Przez element mam na myśli wskaźnik na kontener opakowujący dodatkowo przechowywany obiekt w liście.

Sama klasa listy powinna tylko umieć iterować po takich elementach/kontenerach. W najostrzejszym przepadku lista powinna mieć wskaźnik tylko do pierwszego elementu. Oczywiście dla optymalizacji jeśli często przesuwasz się do tyłu po liście może też mieć ostatni element.
Można do takiej listy stworzyć jeszcze klasę iteratora, który dodatkowo będzie przechowywał wskaźnik na ostatnio odczytywany element, plus będzie mógł być interowany np +1 spowoduje przesunięcie listy o 1 do przodu.

Zabawy co nie miara i można wymyślać i wymyślać. Dużo zależy od przeznaczenia listy i sposobu jej używania.

Nie umiem znaleźć błędu. Twój kod zawiera sporo błędów i musiałem się trochę na kombinować, żeby się w ogóle chciała reszta klas skompilować.
Robisz wiele rzeczy w dziwny sposób, z czego może wynikać problem. To też powoduje, że ciężko mi znaleźć błąd. Nie mam obecnie możliwości poświęcenia za dużo czasu na szukanie problemu, zwłaszcza, że trzeba by było po kolei rozwiązywać problemy. Generalnie napisałeś dużo kodu który nie do końca działa i często nie jest skończony. Bardzo wiele rzeczy masz zakomentowanych tylko po to by w ogóle się skompilowało. To wszystko utrudnia znalezienie przyczyny problemu.

Od siebie mogę dodać, że raczej błąd nie jest w klasach szablonowych, a w klasie magazyn w której te obiekty tworzysz. Chociaż nie dam sobie głowy uciąć bo wszystko wygląda tam w miarę poprawnie.
Taka pierwsza rzecz jaka w oczy mi się rzuciła już w kodzie który tutaj wstawiłeś to choćby ten konstruktor:

InnaL<T>(InnaL* = nullptr,T* = nullptr,InnaL* = nullptr);

Czemu nie przeciążasz konstrutkora i lepiej nie organizujesz jego argumenty - nieobowiązkowe powinny być na końcu, a teraz masz sytuację, że obowiązkowy jest dosłownie w środku i musisz wywoływać konstruktor w taki sposób:

this->p = new InnaL<T>(nullptr,o,nullptr);

lub

sK->UNL(new InnaL<T>(sK,nK,nullptr));

Takich rzeczy niestety jest więcej :frowning:.

1 polubienie

Ogólnie to jest projekt nad którym pracują 4 osoby z czego tylko jedna dodała bodajże kilkanaście linijek kodu a resztę sam zrobiłem po to by można by mogli od czegoś zacząć. W komentarzach też są pozostałości z naprawdę wstępnych rozważań jakby mogło to być zrobione(wizja programu ciągle się rozwija)

Co do kolejności argumentów to na porządkowanie zostawiłem na później a w takim zapisie łatwiej było odczytywać informacje - (poprzedni,aktualny,nastepny) - (nullptr,o,nullptr), póki co to dla mnie najważniejsze jest to aby działało.

Ogólnie to skaczę pomiędzy różnymi językami i nie robiłem zbyt skomplikowanych projektów w c++ więc takie błędy no… muszą być. Z czasem postaram się to poprawić

Jeszcze raz wielkie dzięki za rady i poświęcony czas

Zepsuty (2).zip (5,4 KB)

Tutaj jest minimalny projekt w codeblocksie, który pokazuje ten problem

//////////////////////////////“Main.cpp”
int main()
{}
//////////////////////////////“Magazyn.cpp”
#include “…/include/Magazyn.h”

    Lista<int> Magazyn::listaKurierow;

    void Magazyn::dodajKuriera()
    {
        Magazyn::listaKurierow.DodajNaKoncu();
    }

//////////////////////////////“Magazyn.h”
#ifndef MAGAZYN_H
#define MAGAZYN_H

#include "Lista.h"

class Magazyn
{
    static Lista<int> listaKurierow;

public:
    static void dodajKuriera();

};
#endif

//////////////////////////////“Lista.cpp”
#include “…/include/Lista.h”

    template <class T>
    Lista<T>::Lista(T *obiekt){}

    template <class T>
    void Lista<T>::DodajNaKoncu(){}

//////////////////////////////“Lista.h”
#ifndef LISTA_H
#define LISTA_H

    template <class T>
    class Lista
    {
    public:
        Lista<T>(T* = nullptr);

        void DodajNaKoncu();
    };
    #endif

Tak na szybko - Lista to nie jest klasa, a szablon klasy. Umieść definicję w pliku nagłówkowym, a nie w osobnym cpp.

Czyli:
#ifndef LISTA_H
#define LISTA_H

    template <class T>
    class Lista
    {
    public:
        Lista(T *obiekt = nullptr){}
        void DodajNaKoncu(){}
    };

#endif
2 polubienia

Dzięki, działa.
Muszę jeszcze sprawdzić klasę w realnych zastosowaniach, ale jestem dobrej myśli.

Ja nie wiem jak ja “paczałem”, że nie “dopaczałem” tego konstruktora :smiley:.

@Fizyda To nie jest kwestia konstruktora, tylko sposobu w jaki działają szablony. Szablon jest tutaj informacja, jak kompilator ma tworzyć klasy na podstawie przekazanego parametru.

Jeśli nie robimy tego w pliku nagłowkowym, to kompilator widzi deklaracje - wszystko skompiluje się poprawnie. Niestety linker nie znajdzie nigdzie definicji. Stąd:

undefined reference to Lista::DodajNaKoncu(Kurier*)

Problem można też rozwiązać trzymając się osobnego pliku cpp:

#include "Lista.h"

template <class T>
Lista<T>::Lista(T *obiekt){}

template <class T>
void Lista<T>::DodajNaKoncu(){}

template class Lista<int>;

Wtedy jednak musimy przewidzieć wszystkie możliwe parametry T i je umieścić pod koniec pliku.

To była taśma amatorska, tutaj profesjonalne nagranie ;):
https://isocpp.org/wiki/faq/templates#templates-defn-vs-decl

Ze swojej strony polecam jeszcze przyjrzeć się temu co znajdzie się w pliku Lista.o po kompilacji (np. polecenie nm na Linuksie). Widać jakie zostały wygenerowane symbole. Jeśli zamieścimy deklarację jedynie dla int:

0000000000000000 W _ZN5ListaIiE12DodajNaKoncuEv
0000000000000000 W _ZN5ListaIiEC1EPi
0000000000000000 W _ZN5ListaIiEC2EPi
0000000000000000 n _ZN5ListaIiEC5EPi

Jeśli dla int oraz float:

0000000000000000 W _ZN5ListaIfE12DodajNaKoncuEv
0000000000000000 W _ZN5ListaIfEC1EPf
0000000000000000 W _ZN5ListaIfEC2EPf
0000000000000000 n _ZN5ListaIfEC5EPf
0000000000000000 W _ZN5ListaIiE12DodajNaKoncuEv
0000000000000000 W _ZN5ListaIiEC1EPi
0000000000000000 W _ZN5ListaIiEC2EPi
0000000000000000 n _ZN5ListaIiEC5EPi

Widać tutaj, do czego będzie w stanie odnosić się linker.

2 polubienia

No tak, ale deklaracja szablonu tylko tutaj na forum była podana “w jednym pliku”. Normalnie wszystko było poprawnie pod tym względem. Dodatkowo popatrz na kod, w komentarzach masz opisane co z jakiego pliku pochodzi, więc problemem nie było miejsce umieszczenia deklaracji szablonu, a źle napisana definicja konstruktora - przynajmniej na to wychodzi.

Często na forach czy innych stronach pomocy ludzie wrzucają kod w jeden listing i tylko dodają informację z jakiego pliku co pochodzi.

Dodałem dokładniejsze informacje. Problemem było zawarcie szablonu w pliku cpp bez podania konkretnych instancji szablonu.

Starałem się to zrobić jak najprzystępniej, niestety - szablony ogolnie nie są przystępnym tematem. Tak samo zagadnienia linkera.

1 polubienie

Ehh muszę odpocząć, bo jest rano, a ja nie ogarniam. Teraz doczytałem o definicji w plikach nagłówkowych.
Przyznam szczerze, że z szablonami w C++ miałem tak mało kontaktu, że śmiało mogę powiedzieć, że wcale. No ale dużo się z Twoich postów dowiedziałem, dzięki :slight_smile:.