Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

Zmienne statyczne w template i instancjonowanie w różnych plikach

Ostatnio zmodyfikowano 2020-05-03 10:57
Autor Wiadomość
dlakin95
Temat założony przez niniejszego użytkownika
Zmienne statyczne w template i instancjonowanie w różnych plikach
» 2020-04-30 19:01:04
Cześć,

Ostatnio zacząłem się bawić template'ami i trafiłem na problem, który chciałbym rozwiązać.

Załóżmy przykładowy kod:
Mam plik z definicja template'a ze zmienną statyczną.

template.hpp
C/C++
class TemplateBase {
public:
    virtual int * getA() = 0;
};

template < int a >
class A
    : public TemplateBase
{
public:
    static int a;
    int * getAA() override { return & aa; }
};

template < int a >
int A < a >::a = a;

template class A < 1 >;
template class A < 2 >;

Mam plik żródłowy Example1.cpp z wcześniejszą klasą, która zawiera deklarację metody exec()

C/C++
#include"template.hpp"
#include"example1.hpp"

int Example1::exec() {
    A < 1 >* a = new A < 1 >;
    qDebug() << * a.getA() << a.getA();
}

Mam plik żródłowy Example2.cpp z wcześniejszą klasą, która zawiera deklarację metody exec()

C/C++
#include"template.hpp"
#include"example2.hpp"

int Example2::exec() {
    A < 1 >* a = new A < 1 >;
    qDebug() << * a.getA() << a.getA();
}

I przykładowy kod żródłowy z główną funkcją programu.

C/C++
#include"template.hpp"
#include"example1.hpp"
#include"example2.hpp"

int main( int argc, char *[] argv ) {
    Example1 ex1;
    Example2 ex2;
   
    ex1.exec();
    ex2.exec();
    return 0;
}

Pomijając sens działania kodu oraz wycieki pamięci.

Główny problem polega na tym, że instancja dla klasy Example1 oraz instancja dla klasy Example2 mają wygenerowany różne instancje zmiennej statycznej a.
Dodatkowo rozmiar programu rośnie mocno przy wykorzystaniu go w ten sposób przyjmijmy w 100 plikach.

Jest sposób na to, by powstawała tylko jedna instancja klasy A<1>, a nie aż 50 przy 50 plikach?

P-176749
pekfos
» 2020-04-30 21:48:58
Problem o którym piszesz nie powinien występować, a skoro podany kod nie jest nawet poprawny, zakładam że pytanie wynika z błędnej diagnozy.
P-176752
dlakin95
Temat założony przez niniejszego użytkownika
» 2020-04-30 22:30:55
Może błędnie zdiagnozowałem, ale główny kod, w którym ten problem występuje wykazuje takie objawy jak opisałem.
Kodu nie mam zamiaru umieszczać, bo jest bardzo duży, ale pokrótce opiszę, dlaczego myślę, że to jest przyczyną problemu.

Ogólnie, wykorzystuję szablon do łatwego tworzenia takich pseudoklas.
Mam bazowy szablon Class, który dziedziczy po ClassPriv (który służy za interface).
Mniej więcej coś takiego. Jest to tymczasowe rozwiązanie, które robiłem na szybko i będę to jeszcze optymalizował.
C/C++
template < const char * className >
class Class
    : public ClassPriv
{
    /*Definitions*/
    static Class < className > nullDef;
}

template < const char * className >
Class < className > Class < className >::nullDef = Class < className >;

Klasa wygenerowana przez szablon ma przechowywać definicje klasy.
Na podstawie definicji mogę tworzyć obiekty klas ClassVar.

Każdy taki obiekt może być utworzony dynamiczne globalnie lub w jakimś bloku funkcji lub jako definicja argumentu funkcji (mając na myśli skrypt , który czytam i na jego podstawie tworzę model kodu).
Jeśli tworzę obiekt globalnie lub w bloku funkcji lub jako argument funkcji, to niejawne instancje szablonów znajdują się w jednym pliku żródłowym, np. main.cpp.
Mogę jednak zdefiniować funkcje wbudowane (definiowane przez ze mnie i tylko w razie potrzeby), których argumentami mogą być te wygenerowane klasy ClassVar.
Każda taka funkcja ma swój własny .cpp, który zawiera też implementację funkcji inicjalizacyjnej, w której tworzę definicję wariantów tej funkcji i tam też znajdują się niejawne instancje szablonów.
Teraz przyjmijmy, że zdefiniowałem funkcję exec, która ma 5 wariantów i w sumie wszystkie warianty mają po 3 definicje tej samej klasy ClassVar<1>.
C/C++
int init() {
    /* przykładowo*/
    ClassVar < 1 >* classVar = nullptr;
    append( classVar = new ClassVar < 1 > );
    qDebug() << getStatic(); // Funkcja która zwraca adres zmiennej statyczna nullDef, bo ClassVar widzi też definicję Class
    append( classVar = new ClassVar < 1 > );
    qDebug() << getStatic();
    append( classVar = new ClassVar < 1 > );
    qDebug() << getStatic();
}

Wtedy przy inicjalizacji sprawdzam adres zmiennej statycznej dla wszystkich definicji. Adresy wyjdą np. 0x23, 0x23, 0x23
Ale dla innej funkcji np.exec2, która znajduje się już w innym .cpp otrzymam np. 0x54, 0x54, 0x54
A tam gdzie tworzę obiekt globalnie lub w bloku funkcji lub jako argument funkcji też mam ten sam adres dla całego .cpp (.o) dla konkretnego szablonu, ale inny niż powyższe.
Wiem już też, jak to ominę, a ten kod czeka optymalizacja, jednak w aktualnym stanie bardzo dobrze spełnia swoje zadanie, tylko wynikowy program jest duży.

Dlatego też stworzyłem też ten temat, bo dopiero zacząłem z szablonami i uznałem, że robię coś źle i potrzebowałem poznać wasze zdanie.
Często się zdarza, że odpowiedź jest na tyle oczywista, że nawet w internecie nie ma na to odpowiedzi :P
P-176754
pekfos
» 2020-05-01 17:33:33
Dalej nie podałeś sensownego kodu. Jaki jest związek ClassVar<> z Class<>? Podaj kompletny kod, który reprodukuje problem.
P-176762
dlakin95
Temat założony przez niniejszego użytkownika
» 2020-05-02 13:34:56
Dobra, wiem, co jest przyczyną błędu.

Przez zastosowanie template'a w takiej postaci:

C/C++
const char[] name = "Test"; // Podobnie jest w przypadku static const char[]

class TemplateBase {
public:
    virtual int * getA() = 0;
};

template < const char * a >
class A
    : public TemplateBase
{
public:
    static int a;
    int * getAA() override { return & aa; }
};

template < const char * a >
int A < a >::a = 1;

template class A < name >;

Dla kodu takiego jak w pierwszej wiadomości otrzymuję różne adresy.

Ogólnie, byłem przekonany, że kompilator tworząc stałą, nawet tablicową, jeśli istnieje taka sama, to jej nie kopiuje.
Jednak myliłem się, przez co adresy do template'a są różne dla różnych .o i mam tworzonych kilka wersji templatów.

Ma ktoś pomysł, jak zrobić, by był jeden i ten sam adres?
Albo jak przekazać, by było to samo?

Wiem, że w kodzie tego nie wykorzystuje, ale to jest tylko przykład problemu.
P-176778
pekfos
» 2020-05-02 14:59:43
C/C++
const char[] name = "Test"; // Podobnie jest w przypadku static const char[]
(Kod ma dalej błędy składniowe..) Stała zmienna globalna w C++ ma domyślnie internal linkage, więc będzie miała inny adres w każdej jednostce translacji. Zrób tak:
C/C++
extern const char name[] = "Test";
P-176780
dlakin95
Temat założony przez niniejszego użytkownika
» 2020-05-02 15:15:59
Ale to słowo extern to w każdej jednostce translacyjnej?
P-176782
pekfos
» 2020-05-02 22:10:52
Teraz to definicja z zewnętrznym łączeniem, więc nie możesz mieć więcej niż jednej. Jeśli potrzebujesz to w nagłówku, to musisz tam dać deklarację tablicy, a definicję umieścić w jednej wybranej jednostce translacji. Jedno i drugie tworzy się ze słowem extern, więc krótka odpowiedź brzmi "tak".
P-176793
« 1 » 2
  Strona 1 z 2 Następna strona