Modularna klasa, problem z linkerem

Modularna klasa, problem z linkerem
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

Mam klasę Graphics i chciałbym aby jej zawartość zależała od użytych nagłówków. Czyli jak np. zrobię #include "2D.h" to w klasie powinna się pojawić funkcja tam zdefiniowana. Póki co mam to zrobione tak:

Graphics.h

Kopiuj
struct FrameBuffer; //strukturka zawierająca informacje o uchwycie okna, DC, rozdzielczości itd., jej pełna definicja zalega w nagłówku Display.h.
//Jest on podlinkowany w Graphics.cpp, 2D.cpp i 3D.cpp

#if defined(G2D_H) and defined(G3D_H)
#include "2D.h"
#include "3D.h"
	class GraphicsBase : public Graphics2D, public Graphics3D
	{
	protected:
		GraphicsBase(FrameBuffer& rBuffer) :
			Graphics2D(rBuffer),
			Graphics3D(rBuffer)
		{};
	};
#elif defined(G2D_H)
#include "2D.h"
	class GraphicsBase : public Graphics2D
	{
	protected:
		GraphicsBase(FrameBuffer& rBuffer) :
			Graphics2D(rBuffer)
		{};
	};
#elif defined(G3D_H)
#include "3D.h"
	class GraphicsBase : public Graphics3D
	{
	protected:
		GraphicsBase(FrameBuffer& rBuffer) :
			Graphics3D(rBuffer)
		{};
	};
#else 
	class GraphicsBase
	{
	protected:
		GraphicsBase(FrameBuffer& rBuffer)
		{};
	};
#endif

class Graphics : public GraphicsBase //Klasa właściwa
	{
	private:
		FrameBuffer& Buffer;
	public:
		Graphics(FrameBuffer& rBuffer);
		void Fill(unsigned int uiColor);
	};

2D.h

Kopiuj
#define G2D_H

struct FrameBuffer;

class Graphics2D
{
protected:
	FrameBuffer& Buffer;
public:
	Graphics2D(FrameBuffer& rBuffer);
	void Kwadrat();
}

2D.cpp

Kopiuj
#include "2D.h"
#include "Display.h" //Tu jest definicja structa FrameBuffer

Graphics2D::Graphics2D(FrameBuffer& rBuffer) :
	Buffer(rBuffer)
{}

void Graphics2D::Kwadrat()
{}		

3D.h

Kopiuj
#define G3D_H

struct FrameBuffer;

class Graphics3D
{
protected:
	FrameBuffer& Buffer;
public:
	Graphics3D(FrameBuffer& rBuffer);
	void Szescian();
}

3D.cpp

Kopiuj
#include "3D.h"
#include "Display.h" //Tu jest definicja structa FrameBuffer

Graphics3D::Graphics2D(FrameBuffer& rBuffer) :
	Buffer(rBuffer)
{}

void Graphics3D::Szescian()
{}		

Core.h

Kopiuj
#include "Graphics.h"

class Core //Klasa główna, agregator dla pozostałych
{
public:
   Graphics GTX;
}

Main.cpp

Kopiuj
#include "2D.h"
#include "3D.h"
#include "Core.h"

Teoretycznie działa, bo mogę sobie wywołać np. GTX.Kwadrat() albo GTX.Szescian, wszystko się uruchamia i kompiluje, ale potem program się sypie. Nie odpowiada na komendy, nie działają klawisze, a po kilku sekundach się zawiesza. Co dziwne, jeżeli w Graphics.h podlinkuje 2D.h i 3D.h bezwarunkowo, to wszystko działa normalnie. Z warunkiem (po wykryciu definicji 2D_H i 3D_H) niestety nie.

Ktoś ma jakiś pomysł co robię źle?

edytowany 4x, ostatnio: Crow
Szalony Programista
Szalony Programista
  • Rejestracja:około 7 lat
  • Ostatnio:prawie 4 lata
  • Postów:227
0

A oddzielnie te 3 wersje się kompilują?

Pierwszy program main.c z tylko 2D include g++ kompilacja
Druga tylko z 3D
i 3 z Obiema.

Jeśli ten etap przechodzi można to rozdzielić dodając przy głównym pliku flagę, którą preprocesor odczyta i jeśli ten warunek #ifdef będzie zadeklarowany to się doda.

Najlepiej wrócić do początku i zweryfikować czy te podstawowe wersje działają jak powinny, bo potem będzie znacznie mniej jasne co poszło nie tak, a w ten sposób już pewien problem się rozwiąże czy te rozwiązanie działa, dla każdego z wyjątków.

edytowany 1x, ostatnio: Szalony Programista
Zobacz pozostały 1 komentarz
Szalony Programista
Szalony Programista
Nie jestem pewny jak preprocessor to robi, będę musiał to sprawdzić, ale jeśli pierwszy #if nie zadziałał to skoczy do #elif, ale co jeśli pierwszy #if zadziałał to pomija te następne #elif czy je też sprawdza dla pewności, muszę to sprawdzić potem, ale pewnie pomija chodź nigdy nie sprawdzałem jak działa preprocesora tak naprawdę.
Szalony Programista
Szalony Programista
Ale jak oddzielisz to do oddzielnych plików bez wykorzystywania preprocesora to działa wszystko?
CR
Tak, bez dyrektyw (np. jak wszystko siedzi w jednym pliku) to działa bez zarzutu.
Szalony Programista
Szalony Programista
Ja ci nie pomogę, chyba że mnie wpuścisz na kompa i prze debuguje ci tą aplikację. Może ktoś inny coś wymyśli, ale jak dla mnie nie ma punktu zaczepienia, nie ma jak cokolwiek wymyśleć.
CR
Dokonałem nowych ustaleń, może teraz coś się uda wymyśleć :).
RE
  • Rejestracja:ponad 18 lat
  • Ostatnio:około 2 godziny
0

Ja to bym w ogóle przemyślał konstrukcję takiego softu bo coś mi się widzi że takie kombinacje są nie potrzebne ale pomyśl np. o szablonach
https://stackoverflow.com/questions/16358804/c-is-conditional-inheritance-possible


We are the 4p. Existence, as you know it, is over. We will add your biological and technological distinctiveness to our own. Resistance is futile
Szalony Programista
Szalony Programista
  • Rejestracja:około 7 lat
  • Ostatnio:prawie 4 lata
  • Postów:227
0

Błąd musi być gdzieś w kodzie, próbowałeś jakieś breakpointy ustawiać żeby sprawdzić, w którym miejscu się wykłada program?

_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
1

Brak strażników w plikach .h

Kopiuj
#include "2D.h"
#include "3D.h"
#include "Core.h" => #include "Graphics.h" => #include "2D.h" and/or #include "3D.h"

Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
Szalony Programista
Szalony Programista
W sumie nigdzie #pragma once nie zrobił ani żadnego #ifdef __2D_H, chyba to był problem.
Szalony Programista
Szalony Programista
Niby człowiek wie, że nie powinno się includować 2 razy tego samego pliku, a nawet nie zauważył xd
CR
Wszędzie są pragmy, w każdym .h
_13th_Dragon
To czemu ich nie widzimy? Nie rozumiem, specjalnie #pragma once wycinałeś przed wklejeniem tu?
CR
Część kodu tutaj to wklejka, część pisana ręcznie, już na forum. Nie mogłem wkleić kilkuset linijek kodu, więc na szybko przygotowałem coś, co oddaje schemat, a w rzeczywistości wygląda to inaczej.
Szalony Programista
Szalony Programista
  • Rejestracja:około 7 lat
  • Ostatnio:prawie 4 lata
  • Postów:227
0

Po tych skrawkach konstruktorów class wydaje się wszystko ok, FrameBuffer to specyficzna nazwa, robisz swój system?

Ja bym to zdebugował tak czy siak, to jest najłatwiejszy sposób namierzenia problemu.

Czasem problem może siedzieć gdzieś głębiej, jak klawiatura siada i inne rzeczy to to w samym prologu konstruktora nie doświadczymy.

Szalony Programista
Szalony Programista
  • Rejestracja:około 7 lat
  • Ostatnio:prawie 4 lata
  • Postów:227
0

Ogólnie jak system działa na jednym rdzeniu to musi powiadomić system, że odczyt z stdin np. ma być O_NONBLOCK wtedy asynchronicznie odczytuje bez blokowania aplikacji, chyba że jest kilka wątków.

Czyli jak działał na jednym wątku i coś się zwaliło to zawiesiło całą aplikację.

Szalony Programista
Szalony Programista
  • Rejestracja:około 7 lat
  • Ostatnio:prawie 4 lata
  • Postów:227
0

To jest na pewno jakiś błahy błąd, jak się znajdzie miejsce gdzie to się wykłada, to łatwo się zrozumie co było powodem błędu.
Debugowanie jest trudne i dlatego nikt tego nie lubi.

Ja mogę się założyć, o flaszkę, że to na bank będzie coś głupiego i prostego co wykładało cały program, ale dopiero namierzenie miejsca błędu umożliwiło zrozumienie dlaczego tak się dzieje.

TomaszLiMoon
  • Rejestracja:prawie 10 lat
  • Ostatnio:około 15 godzin
  • Postów:530
1

Jak @revcorey wcześniej doradził pomyśl o użyciu w tym przypadku szablonów. Zobacz przykład wykorzystujący variadic templates w dziedziczeniu.

Kopiuj
#include <iostream>

using namespace std;

struct DummyBuffer{};

class Graphics2D
{
protected:
    DummyBuffer& Buffer;
public:
    Graphics2D( DummyBuffer& rBuffer ) : Buffer{rBuffer} {}
    void Kwadrat(){ cout << "Kwadrat\n"; }
};

class Graphics3D
{
protected:
    DummyBuffer& Buffer;
public:
    Graphics3D(DummyBuffer& rBuffer) : Buffer{rBuffer} {}
    void Szescian(){ cout << "Szescian\n"; }
};

template< typename... Interfaces >
class GraphicsBase : public Interfaces...
{
  public:
    GraphicsBase(DummyBuffer& rBuffer) : Interfaces(rBuffer)... {}
};

int main()
{
    DummyBuffer someBuffer;

    GraphicsBase Base {someBuffer};
    GraphicsBase<Graphics2D> Base2D {someBuffer};
    GraphicsBase<Graphics3D> Base3D {someBuffer};
    GraphicsBase<Graphics2D,Graphics3D> BaseAll {someBuffer};

    Base2D.Kwadrat();
    Base3D.Szescian();
    BaseAll.Kwadrat();
    BaseAll.Szescian();
}

https://godbolt.org/z/8Mxnoh5Px

edytowany 1x, ostatnio: TomaszLiMoon
CR
Ale mi zależy na strukturze modularnej całego projektu. Doklejasz include'a i określone klasy "z automatu" się rozbudowują, bez absolutnie żadnych dodatkowych zmian w kodzie. W twoim sposobie musisz ręcznie "rozszerzać" dziedziczenie każdej z klas i znać nazwy tych nowych klas, znajdujących się w dodanych w nagłówkach. Ja tego chcę uniknąć, dodajesz nagłówek i ma działać.
_13th_Dragon
Ale co ma jedno do drugiego? Na .cpp i .h nie umiesz rozbić?
CR
Możesz jaśniej?
TomaszLiMoon
Ale mi zależy na strukturze modularnej całego projektu - może ktoś mający w tej dziedzinie więcej doświadczenia wypowie się na ten temat, czy takie podejście jest najlepszym rozwiązaniem. IMHO gdy mamy do czynienie z klasą GraphicsBase której implementacja zależy od flagi kompilacji, to wywołanie np. metody Szescian() jest ściśle powiązane z daną gałęzią kompilacji, co może zwiększać ryzyko pomyłki w kodzie.
_13th_Dragon
@Crow, czy chodzi ci o to że chcesz to wpakować do biblioteki i udostępniać komuś w różnych kombinacjach: 2D,3D? Jeżeli tak to zastanów się nad wzorcem projektowym Fasada. Czyli biblioteka zawiera 2D oraz 3D natomiast na zewnątrz udostępniasz tylko jedną z trzech fasad, ewentualnie jedną z opcjonalnie przykrytymi metodami za pomocą #define.
_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
0

Może powinieneś użyć wzorca projektowego plugin?


Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

Uprościłem przykładowy kod, żeby wywalić wszystko co zbędne. Nadal wychodzą mi dziwne efekty. Mam coś takiego (teraz to już 100% wklejki):

Base.h

Kopiuj
#pragma once

//#define TRIGGER - BARDZO WAŻNA WSKAZÓWKA!!!

#if defined(TRIGGER)
#include "Extra.h"
#endif

namespace ce
{
#if defined(TRIGGER)
	class Base : public Extra
	{
	public:
		Base(int& iPassThing);
		void BaseCall();
	};
#else
	class Base
	{
	public:
		Base(int& iPassThing);
		void BaseCall();
	};
#endif
}

Base.cpp

Kopiuj
#include "Base.h"
#include <iostream>

namespace ce
{
#if defined(TRIGGER)
	Base::Base(int& iPassThing) :
		Extra(iPassThing)
	{
	}
#else
	Base::Base(int& iPassThing)
	{
	}
#endif

	void Base::BaseCall()
	{
		std::cout << "Base Call" << std::endl;
	}
}

Extra.h

Kopiuj
#pragma once

namespace ce
{
	class Extra
	{
	public:
		int& Thing;
		Extra(int& iThing);
		void ExtraCall();
	};
}

Extra.cpp

Kopiuj
#include "Extra.h"
#include <iostream>

namespace ce
{
	Extra::Extra(int& iThing) :
		Thing(iThing)
	{
	}
	
	void Extra::ExtraCall()
	{
		std::cout << "Extra Call" << std::endl;
	}
}

Core.h

Kopiuj
#pragma once
#include "Base.h"

namespace ce
{
class Core
	{
	public:
		int SomeGarbage = 100;
		Base B;
		Core();
	};
}

Core.cpp

Kopiuj
#include "Core.h"

namespace ce
{
	Core::Core()  :
		B(SomeGarbage),
	{
	}
}

Main.cpp

Kopiuj
#define TRIGGER
#include "Core.h"

Zwróćcie proszę uwagę na kolejność. Na samiutkim początku (w Main.cpp) pojawia się definicja TRIGGER. Po niej zostaje zalinkowane Core.h, które to linkuje Base.h. Czyli w momencie "wywołania" Base.h, definicja TRIGGER już istnieje, prawda? No tak się zdaje i rzeczywiście blok kodu zależny od istnienia tej definicji TRIGGER zostaje skompilowany ale występuje błąd, o którym pisałem wcześniej (nic nie odpowiada, a po kilku sekundach się sypie). Co ciekawe, wszystko jest OK, gdy definicja TRIGGER nie następuje na początku Main.cpp, a dopiero na początku Base.h (zobaczcie wykomentowany fragment tego nagłówka). To jest dziwne, bo przecież teoretycznie nie przestawia to nic w kolejności linkowania, nadal najpierw zostaje wywołane Core.h, które to wywołuje Base.h, które to zawiera definicję TRIGGER. Pomyślałem, że może z jakiegoś powodu Core.h musi być wywołane jako pierwsze, a definicja TRIGGER może się pojawić dopiero wtedy, gdy Core.h zostało już zalinkowane... ale nie. Gdyby obie definicje pojawiają się równocześnie (zarówno w Main.cpp jak i w Base.h) wszystko działa jak trzeba, mimo że przecież teoretycznie ta z Main.cpp nadal jest wywoływana jako pierwsza, jeszcze przed wywołaniem Core.h...?

Rozumie coś ktoś z tego?

EDIT

Dodatkowo problem znika po wywaleniu z klasy Extra pola Thing. Nawet po przerobieniu go na zwykłego inta (a nie referencję) i wywaleniu jego przypisywania w konstruktorze (zastępując je całkowitym brakiem inicjalizacji lub przypisując mu wartość początkową na płasko - int Thing = 100) nadal się sypie. Dopiero całkowite skasowanie pola usuwa problem. Innymi słowy, klasa Extra bez pól działa, z polami (jakimikolwiek, zainicjowanymi lub nie) nie działa. No chyba, że TRIGGER pojawia się w Base.h, wtedy pola nie przeszkadzają. Całkowite wywalenie konstruktorów z klas Base i Extra też nie pomaga, o ile ta druga ma pola.

edytowany 7x, ostatnio: Crow
nalik
Terminologia się nie zgadza. Linkowanie to nie samo co "inkludowanie". kolejność inkludów nie definiuje kolejności linkowania.
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

Kolejne testy pokazują, że to nie ma nic wspólnego z linkowaniem, tylko z dyrektywami! Spójrzcie na ten kod:

Base.h

Kopiuj
#pragma once
#include <iostream>

//#define TRIGGER

namespace ce
{
	class Extra
	{
	public:
		int JakiesPole = 100;
		void ExtraCall()
		{
			std::cout << "Extra Call" << std::endl;
		};
};
	
	class Base
#if defined(TRIGGER)
	: public Extra
#endif
	{
	public:
		void BaseCall()
		{
			std::cout << "Base Call" << std::endl;
		};
	};
}

Wystarczy "odkomentować" #define TRIGGER (#define TRIGGER w Main.cpp nadal istnieje) albo "wykomentować" JakiesPole i problem znika.

edytowany 2x, ostatnio: Crow
CR
Całkowite wyrzucenie nagłówka Base.h i przeniesienie wszystkiego do Core.h (który to posiada instancję klasy Base) nadal wywołuje błędy, więc to na 100% problem z dyrektywami.
_13th_Dragon
spróbuj #ifdef TRIGGER
RE
  • Rejestracja:ponad 18 lat
  • Ostatnio:około 2 godziny
0

Takie rzeczy jak jakieś flagi to się przeważnie w jakiś makach robi a nie w kodzie CPP bezpośrednio. trzy po trzy śledzę temat. Co ty chcesz ostatecznie osiągnąć?


We are the 4p. Existence, as you know it, is over. We will add your biological and technological distinctiveness to our own. Resistance is futile
edytowany 1x, ostatnio: revcorey
CR
Jak już pisałem, modularną klasę. Np. bazowo masz funkcję z klasy Figury.Kwadrat() ale jak dodasz nagłówek #include Kolo.h, to w klasie Figury pojawi ci się Figury.Kolo()itd. Piszę rodzaj frameworka, który może robić wiele rzeczy, ale nie wszystkie są potrzebne w każdym projekcie, więc nie wszystko musi być zalinkowane do klasy fasadowej, a tylko to, co jest akurat przydatne. Kod ma być rozbity na paczki, zgromadzone pod jednym nagłówkiem (np. Grafika 2D, Grafika 3D, Fizyka itp).
RE
  • Rejestracja:ponad 18 lat
  • Ostatnio:około 2 godziny
1

Jak już pisałem, modularną klasę. Np. bazowo masz funkcję z klasy Figury.Kwadrat() ale jak dodasz nagłówek #include Kolo.h, to w klasie Figury pojawi ci się Figury.Kolo()itd. Piszę rodzaj frameworka, który może robić wiele rzeczy, ale nie wszystkie są potrzebne w każdym projekcie, więc nie wszystko musi być zalinkowane do klasy fasadowej, a tylko to, co jest akurat przydatne.

Całkowicie niepotrzebna gmatwanina. Co więcej niezbyt to dla mnie intuicyjne. Ja chciałbym mieć stabilne API klasy tak jak masz np. w qt i sprawdzić w dokumentacji co i jak. A nagle w projekcie mają mi się pojawiać klasy o tej samej nazwie z różnym interfejsem bo ktoś dodał kolo.h. A jak trzeba będzie jednocześnie użyć kolo.h i trojkat.h w tym samym pliku to co się ma stać? A jak wielu ludzi będzie pracować nad kodem to co? Widzę egzemplarz klasy X i się okazuje że ma on inny interfejs bo ktoś przede mną dodał kolo.h.

Klasa teoretycznie powinna robić jedną rzecz albo obsługiwać jakiś mechanizm znowu sięgnę do qt. masz tam abtractmodel a po nim dziedziczą inne bardziej szczegółowe modele. Przyjrzyj się założeniom Qt.

Moje osobiste zdanie jest takie że źle zabierasz się do tematu. I nie wiem co chcesz zyskać takim podejściem.

A co do dyrektyw powtórzę dodaj je w make.


We are the 4p. Existence, as you know it, is over. We will add your biological and technological distinctiveness to our own. Resistance is futile
edytowany 1x, ostatnio: revcorey
CR
Masz główną klasę fasadową o nazwie Graphics. Jak chcesz zrobić COKOLWIEK związanego z grafiką, to wszystkie funkcje znajdziesz właśnie tam, np. narysujesz obrazek, zmienisz kolor tła, ustawisz gradient itd. No ale załóżmy, że chciałbyś teraz wyrenderować model 3D. Nic prostszego, dodajesz nagłówek 3D.h i w klasie Graphics "magicznie" pojawią się funkcje pokroju RenderModel() albo ResetZBuffer(). Dodatkowo też w przestrzeni nazwowej ce::graphics:: pojawią się nowe struktury i klasy, np. Mesh, Matrix3D, albo Vector3D, potrzebne do pracy z 3D.
CR
Co do zasady jedna klasa jedna rzecz, to się zgadzam. Dlatego właśnie Graphics to tylko fasada, klasa pełniąca rolę interface'u, swego rodzaju agregator koordynujący "zakulisowe" działanie bardzo wielu, bardzo małych klas. W zamyśle ma to ułatwić zewnętrznemu użytkownikowi (wykorzystującemu framework) korzystanie z niego. Jeżeli jakiejś funkcji dotyczącej grafiki nie ma w klasie Graphics, to po prostu nie istnieje. Jak dla mnie to wygodne i eleganckie rozwiązanie, z którego sam lubię korzystać.
enedil
Niezbyt to eleganckie. Jak w jednym pliku .cc zainkludujesz 3D.h, a w innym nie, to zapewne nawet jeśli kompilacja się uda, to albo linkowanie się wywali, albo będą segfaulty.
RE
brzmi dla mnie jak katastrofa do tego utrzymanie i zmiany w API będą nie jasne. Cóż rób jak chcesz.
Szalony Programista
Szalony Programista
  • Rejestracja:około 7 lat
  • Ostatnio:prawie 4 lata
  • Postów:227
0

Hmm, odpal w ida pro/ghidra czy innym disassemblerze i porównaj działającą aplikację i tą wygenerowaną z dyrektywami preprocesora.

Mi taka analiza często pomagała gdy np. linkier w złym miejscu kod położył czy inne problemy, sprawdziłem adresy relatywne itp.

Tak chyba będzie najszybciej.

GS
  • Rejestracja:ponad 8 lat
  • Ostatnio:dzień
  • Postów:1265
1
Crow napisał(a):

Wystarczy "odkomentować" #define TRIGGER (#define TRIGGER w Main.cpp nadal istnieje) albo "wykomentować" JakiesPole i problem znika.

Nie wiem czy dobrze zrozumiałem, ale czy oczekujesz że wystarczy #define TRIGGER w main.cpp, żeby wszędzie indziej miało efekt? TRIGGER trzeba zdefiniować w każdej jednostce kompilacji, czyli wszystkie pliki cpp muszą albo mieć albo nie mieć to zdefiniowane. Takie rzeczy definiuje się na poziomie całego projektu, np. w pliku CMakeLists.txt i przekazuje tą definicję do targetu.

edytowany 1x, ostatnio: GutekSan
RE
już mu o tym pisałem ale nie wiem czy wie dokładnie jak to zrobić.
CR
No właśnie nigdy czegoś takiego nie robiłem i byłbym wdzięczny za jakieś wskazówki.
Szalony Programista
Szalony Programista
@Crow: a sprawdzałeś jak to wygląda w binarce wygenerowanej?
RE
wskazówka jest prosta. W systemie budowania jaki używasz dorzuć sobie flagi.
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

Zrobiłem coś takiego:

Main.cpp

Kopiuj
#define CHECK
#include "Core.h"

Core.h

Kopiuj
#pragma once

#ifdef CHECK
#define TRIGGER
#endif

class Extra
	{
	public:
		int JakiesPole = 100;
		void ExtraCall()
		{
			std::cout << "Extra Call" << std::endl;
		};
	};

	class Base
#ifdef TRIGGER
	: public Extra
#endif
	{
	public:
		void BaseCall()
		{
			std::cout << "Base Call" << std::endl;
		};
	};

I robią się błędy, chociaż w Core.h "podświetla" się ta parta kodu, która powinna zostać skompilowana w razie wykrycia definicji TRIGGER (chyli kompilator uznaje, że warunek został spełniony - CHECK wykryto). Gdy natomiast wywalam sprawdzenie CHECK z Main.cpp i zostawiam bezwarunkowe #define TRIGGER w Core.h, wtedy działa bez zarzutu. Czyli kompilator niby poprawnie reaguje na obecność CHECK w Main.cpp, bo wie które partie kodu ma wykorzystać, ale jednak kompiluje z błędami.

_13th_Dragon
#ten define przenieś do opcji kompilatora
_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:2 miesiące
0

Może jedno z trzech:

1:

Kopiuj
#include <iostream>
#include <memory>
using namespace std;

//#define TRIGGER

class BaseBase
{
	public:
	virtual void BaseCall() { cout<<"Not avaliable (moze wyjatek?)"<<endl; }
	virtual void ExtraCall() { cout<<"Not avaliable (moze wyjatek?)"<<endl; }
	virtual ~BaseBase() {}
};

class BaseInterface:public BaseBase
{
	public:
    virtual void BaseCall() { cout<<"Base Call"<<endl; }
};

class ExtraInterface:public BaseInterface
{
	public:
	virtual void ExtraCall() { cout<<"Extra Call"<<endl; }
};

#ifdef TRIGGER
typedef ExtraInterface UsedInterface;
#else
typedef BaseInterface UsedInterface;
#endif

class UsedClass:public UsedInterface
{
};

int main()
{
	UsedClass bc;
	bc.BaseCall();
	bc.ExtraCall();
}

2:

Kopiuj
#include <iostream>
#include <memory>
using namespace std;

//#define TRIGGER

class BaseBase
{
	public:
	void BaseCall() { cout<<"Base Call"<<endl; }
	void ExtraCall() { cout<<"Extra Call"<<endl; }
	void Unavailable() { cout<<"Not avaliable (moze wyjatek?)"<<endl; }	
};

class BaseFasade
{
	private:
	BaseBase bb;	
	public:
    void BaseCall() { bb.BaseCall(); }
    void ExtraCall() { bb.Unavailable(); }
};

class ExtraFasade
{
	private:
	BaseBase bb;	
	public:
    void BaseCall() { bb.BaseCall(); }
    void ExtraCall() { bb.ExtraCall(); }
};

#ifdef TRIGGER
typedef ExtraFasade UsedFasade;
#else
typedef BaseFasade UsedFasade;
#endif

class UsedClass:public UsedFasade
{
};

int main()
{
	UsedClass bc;
	bc.BaseCall();
	bc.ExtraCall();
}

3:

Kopiuj
#include <iostream>
#include <memory>
using namespace std;

//#define ADD
//#define BASE
#define EXTRA

class BaseBase
{
	public:
	void Unavailable() { cout<<"Not avaliable (moze wyjatek?)"<<endl; }	
	void AddCall() { Unavailable(); }
	void BaseCall() { Unavailable(); }
	void ExtraCall() { Unavailable(); }
	virtual ~BaseBase() {}
};

class UsedClass:public BaseBase
{
	public:
#ifdef ADD
	void AddCall() { cout<<"Add Call"<<endl; }
#endif
#ifdef BASE
	void BaseCall() { cout<<"Base Call"<<endl; }
#endif
#ifdef EXTRA
	void ExtraCall() { cout<<"Extra Call"<<endl; }
#endif
};

int main()
{
	UsedClass uc;
	uc.AddCall();
	uc.BaseCall();
	uc.ExtraCall();
}

Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
edytowany 1x, ostatnio: _13th_Dragon
Zobacz pozostałe 2 komentarze
CR
No wszystkie wersje dziedziczą po BaseBase, które w twojej wersji musi zawierać zbitek wszystkich innych klas składowych, czyli nici z modularności.
_13th_Dragon
Pierwsza wersja BaseBase ma właściwie tylko interface nic więcej.
CR
Samo jej istnienie przekreśla to co chcę zrobić.
_13th_Dragon
W takim razie widzę że to typowy problem XY - powiedz co chcesz osiągnąć a nie jak to chcesz zrobić.
CR
Napisałem już z 5 razy, najdokładniej w komentarzu do posta @recovery
enedil
  • Rejestracja:ponad 11 lat
  • Ostatnio:około 12 godzin
  • Postów:1027
3

Mam wrażenie, że próbujesz wymyślić na nowo
https://en.m.wikipedia.org/wiki/Curiously_recurring_template_pattern

Dzięki temu obejdzie się bez żadnego użycia preprocesora, jednocześnie nie pozbawiając siebie statycznych checków od kompilatora, czy metoda powinna istnieć czy nie (tak jak w pierwszym przykładzie Dragona).

edytowany 2x, ostatnio: enedil
Zobacz pozostałe 11 komentarzy
RE
@enedil: coś mi się źle chyba skopiowało. W każdym razie ja uważam tamto rozwiązanie za złe.
enedil
@Crow: bo jak już pisałem, robi się mocny kłopot jeśli jedna jednostka kompilacji włącza inne nagłówki niż druga. Prawie kodu jeszcze nie ma, a już taki design sprawia sporo kłopotów.
CR
@recovery - To znaczy tu nie ma zagrożenia, że będą jakieś konflikty nazw, bo Graphics w ogóle nie służy do rozbudowy przez użytkownika. W moim framework'u jest abstrakcyjna klasa fasadowa, która nazywa się Core i to ona, jako jedyna, jest udostępniona na zewnątrz do dziedziczenia. Zawiera w sobie inne klasy (interface'y), odpowiedzialne za poszczególne funkcjonalności, np. Graphics, Sound, Keyboard, Mouse itd. I nie, nie pracowałem zawodowo, bo nie jestem programistą, tylko samoukiem, hobbystą (a z zawodu adwokatem czyli bezużytecznym humanistą ;d).
nalik
@Crow: Posłuchaj @enedil i @recovery . To co próbujesz zrobić jest - mówiąc brutalnie - słabe, tzn. trudne w utrzymaniu, debugowaniu, a także nieintuicyjne w użytkowaniu. Czasami warto zrobić krok w tył i zastanowić się czy podążasz w odpowiednim kierunku. Jak Ci doświadczeni programiści mówią, że nie tędy droga, to warto choć przez chwilkę się nad tym zastanowić.
CR
@nalik - Ale ja wcale się nie upieram, że mam rację i że mój pomysł jest świetny. Po prostu chciałem poznać argumentację przeciwników takiego rozwiązania, by dzięki temu zrozumieć co powinno, a czego nie powinno się robić i dlaczego. Samo "tak nie rób", jest mało przydatne, ale już wyjaśnienie tego stanowiska może być bardzo pomocne. No i chętnie też dowiedziałbym się, dlaczego to co robię, nie działa, tj. dlaczego kompilator zachowuje się w taki sposób. Myślałem, że może ktoś będzie potrafił to stwierdzić.
nalik
  • Rejestracja:około 9 lat
  • Ostatnio:prawie 2 lata
  • Postów:1039
5

Co ciekawe, wszystko jest OK, gdy definicja TRIGGER nie następuje na początku Main.cpp, a dopiero na początku Base.h

Jak TRIGGER jest w main.cpp, to plik base.cpp nie widzi TRIGGER. Dla pliku main.cpp klasa Base dziedziczy po Extra. Dla base.cpp nie dziedziczy. Masz 2 definicje tej samej klasy, które mają różny układ w pamięci i niezainicjowaną klasę bazową.

Dodatkowo problem znika po wywaleniu z klasy Extra pola Thing.

Bo po wywaleniu pola thing klasa Extra nie wpływa na rozmiar klasy dziedziczącej - nie ma żadnych pól - więc znika problem 2 różnych układów w pamięci dla obiektów tego samego typu.

Mam nadzieję, że to wystarczy by przekonać Cię, dlaczego ten pomysł jest chybiony.

Dodatkowo, mylisz linkowanie z "inkludowaniem" (dodawaniem nagłówków) albo nie rozumiesz jak przebiega proces wytworzenia pliku wykonywalnego. Nagłówki dodaje preprocesor i w praktyce sprowadza się to do zwykłego wklejenia zawartości nagłówków do innego pliku. Jest to pierwszy etap w procesie. Linkowanie - czy też po polsku konsolidacja - to łączenie ze sobą różnych plików z już skompilowanym kodem w nowy plik ze skompilowanym kodem. Za ten etap odpowiada linker i jest to ostatni etap w procesie. Kolejność dołączonych nagłówków nie ma wpływu na kolejność linkowania plików.

edytowany 4x, ostatnio: nalik
CR
Tak, teraz to ma sens, dziękuję za pomoc i praktyczne wyjaśnienia :).
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

Ustaliliśmy, że mój pomysł nie był dobry, jakiej zatem powinienem użyć alternatywy, by osiągnąć mniej więcej podobny efekt (modularność)? Dziedziczenie przez template i parameter pack? A może lepiej zastąpić parameter pack to przy pomocy initializer_list? W przypadku klas upakowanych w parameter packu da się w ogóle zrobić tak, żeby każda z klas miała inny konstruktor, przyjmujący inne argumenty? Czy też wszystkie muszą mieć identyczny konstruktor?

edytowany 1x, ostatnio: Crow
nalik
  • Rejestracja:około 9 lat
  • Ostatnio:prawie 2 lata
  • Postów:1039
0

Ja dalej nie rozumiem jaki to ma być efekt i jaki problem rozwiązuje. Bo modularność, to trochę coś innego niż opisujesz. C++20 ma wsparcie dla modułów, ale nie działają tak jakbyś oczekiwał. Modularność to sposób podziału kodu na mniejsze jednostki, zazwyczaj pod względem dostarczonej funkcjonalności. Polimorfizm - czy to statyczny czy dynamiczny - to nie modularność.

edytowany 2x, ostatnio: nalik
CR
Generalnie chcę osiągnąć efekt, w którym zawartość klasy fasadowej Core, będzie zależna od wyboru użytkownika. Czyli jak chce mieć dostęp np. do funkcji 3D, to podaje klasę 3D jako parametr w konstruktorze fasady i wtedy te funkcje będą tam widoczne. Nawet niekoniecznie w Core.Graphics, tylko np. w Core.Graphics3D, czyli jako osobny interface. I nie chodzi tu o żaden problem, tylko sposób organizacji kodu i pracy z frameworkiem.
nalik
  • Rejestracja:około 9 lat
  • Ostatnio:prawie 2 lata
  • Postów:1039
2

Generalnie chcę osiągnąć efekt, w którym zawartość klasy fasadowej Core, będzie zależna od wyboru użytkownika. Czyli jak chce mieć dostęp np. do funkcji 3D, to podaje klasę 3D jako parametr w konstruktorze fasady i wtedy te funkcje będą tam widoczne. Nawet niekoniecznie w Core.Graphics, tylko np. w Core.Graphics3D, czyli jako osobny interface. I nie chodzi tu o żaden problem, tylko sposób organizacji kodu i pracy z frameworkiem.

Generalnie klasa powinna mieć zdefiniowaną funkcjonalność, a nie morfować według widzimisię użytkowników-developerów. Idealnie uważną się, że w programowaniu obiektowym klasa powinna odpowiadać za pojedyncza funkcjonalność. Zobacz zasadę SOLID SRP

Można sterować tym co robi jakiś kawałek kodu poprzez preprocesor. Nie jest to piękne, ani łatwe w czytaniu, ale się tego używa. Tylko zdefiniowanie co robi klasa powinno być spójne wewnątrz projektu, a nie tak jak zrobiłeś to Ty, tzn definy powinny być ustalone raz podczas czasu kompilacji. Użytkownik przekompiluje sobie Twój kod ze swoimi zmiennymi preprocesora, właczając to co potrzebuje. Z tego co pamiętam, zostało to już zasugerowane na początku dyskusji przez kogoś. Raczej stosuje się to w programowaniu niskopoziomowy do obsługi różnych architektur lub konfigurowalnych podczas kompilacji wersji produktu.

Ale funkcjonalność klasy powinna być niezmienna, spójrz na kolejną zasadę z serii SOLID - open-close. Jakieś magiczne morfowanie tego co klasa wystawia w zależności od czegoś innego ... to nie zadziała w cpp zbyt dobrze. Potrzebujesz osobnych klas.

Można natomiast zmieniać zachowanie konkretnych instancji klasy. Wystarczy, że opcje zostaną przekazane do nowo tworzonej instancji klasy fasadowej w konstruktorze. Kod musiałby sprawdzać stan opcji, by wiedzieć, które kawałki kodu uruchomić. W Twoim przypadku nie jest to znowu zbyt piękne. Nie ukrywa też zbędnych metod z klasy, bo te po prostu do niej przynależą, mogą co najwyżej rzucać wyjątkiem lub nic nie robić, co jest bardzo słabe. Ale też się czasami stosuje, choć raczej w inny sposób, sterując na przykład protokołem komunikacji.

Innym podejściem, który rozwiązuje zbliżony problem, są mixiny, czyli klasy, które dostarczają konkretną funkcjonalność, gotową do wykorzystania w klasach dziedziczących. Odpowiednie zastosowanie wieledziedziczenia. Zamiast dostarczać użytkownikowi jakąś klasę fasadową możesz mu dostarczyć klasy dostarczające funkcjonalność i wymagać od niego by dziedziczył po klasach, których funkcjonalności potrzebuje. I to może być jak najbardziej zastosowanie dla CRTP, aczkolwiek można też pominąć szablony w implementacji.

Możesz też dostarczyć różne kombinacje klas core zapewniających odpowiednią funkcjonalność. Pod spodem wykorzystać na przykład kompozycję albo mixiny. Wtedy rzeczywiście osiągnąłbyś efekt widoczności odpowiednich metod. Tylko żeby tych kombinacji nie było zbyt dużo, bo to jest znowu bez sensu.

Na koniec - zapoznaj się z zasadami SOLID i sprawdź, czy nie próbujesz umyślnie złamać którejś z nich.

edytowany 10x, ostatnio: nalik
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0

Zamiast dostarczać użytkownikowi jakąś klasę fasadową możesz mu dostarczyć klasy dostarczające funkcjonalność i wymagać od niego by dziedziczył po klasach, których funkcjonalności potrzebuje.`

Chciałbym mieć jedno i drugie. Fasadową klasę bazową Core, która dostarcza kilku metod abstrakcyjnych niezbędnych do działania całości (jest to prosty game engine z możliwością renderowania 2D i 3D), np. OnUserUpdate() albo OnUserInput(), natomiast cała reszta powinna zależeć od wyboru użytkownika, np. właśnie w formie dziedziczenia po tych klasach. Czyli użytkownik MUSI dziedziczyć po Core, gdzie ma zebrane esencjonalne dla całości elementy, natomiast inne składniki (klasy) to już wedle potrzeby. Chciałbym ro mieć w konstruktorze dla Core, np. w formie variadic template. To jest OK podejście?

edytowany 5x, ostatnio: Crow
nalik
  • Rejestracja:około 9 lat
  • Ostatnio:prawie 2 lata
  • Postów:1039
1
Crow napisał(a):

Zamiast dostarczać użytkownikowi jakąś klasę fasadową możesz mu dostarczyć klasy dostarczające funkcjonalność i wymagać od niego by dziedziczył po klasach, których funkcjonalności potrzebuje.`

Chciałbym mieć jedno i drugie. Fasadową klasę bazową Core, która dostarcza kilku metod abstrakcyjnych niezbędnych do działania całości (jest to prosty game engine z możliwością renderowania 2D i 3D), np. OnUserUpdate() albo OnUserInput(),

Na czym polega fasadowość tej klasy? Dla mnie brzmi jak interfejs, który musi zaimplementować użytkownik, czyli w CPP abstrakcyjna klasa bazowa. Bo kiedy mówisz o klasie fasadowej, to ja myślę o wzorcu fasada

natomiast cała reszta powinna zależeć od wyboru użytkownika, np. właśnie w formie dziedziczenia po tych klasach. Czyli użytkownik MUSI dziedziczyć po Core, gdzie ma zebrane esencjonalne dla całości elementy, natomiast inne składniki (klasy) to już wedle potrzeby. Chciałbym ro mieć w konstruktorze dla Core, np. w formie variadic template. To jest OK podejście?

To ustalmy jeszcze jedno - co się stanie jak użytkownik dokona jakiegoś wyboru? Jaki to ma wpływ na funkcjonalność?

edytowany 1x, ostatnio: nalik
CR
  • Rejestracja:ponad 16 lat
  • Ostatnio:11 miesięcy
0
nalik napisał(a):

To ustalmy jeszcze jedno - co się stanie jak użytkownik dokona jakiegoś wyboru? Jaki to ma wpływ na funkcjonalność?

Ja bym to widział tak. Użytkownik dziedziczy tylko po Core, więc dostaje tylko niezbędne funkcje, czyli np. Core.Graphics.ClearScreen() albo Core.Graphics.DrawSquare(). Ale jak zrobi dziedziczenie po np. Sound, to dostanie dostęp do Core.Sound.PlaySound(), a jak po Keyboard, to zyska dostęp do Core.Keyboard.GetKeyState() itd. To jest poprawne?

edytowany 1x, ostatnio: Crow
nalik
  • Rejestracja:około 9 lat
  • Ostatnio:prawie 2 lata
  • Postów:1039
1
Crow napisał(a):
nalik napisał(a):

To ustalmy jeszcze jedno - co się stanie jak użytkownik dokona jakiegoś wyboru? Jaki to ma wpływ na funkcjonalność?

Ja bym to widział tak. Użytkownik dziedziczy tylko po Core, więc dostaje tylko niezbędne funkcje, czyli np. Core.Graphics.ClearScreen() albo Core.Graphics.DrawSquare(). Ale jak zrobi dziedziczenie po np. Sound, to dostanie dostęp do Core.Sound.PlaySound(), a jak po Keyboard, to zyska dostęp do Core.Keyboard.GetKeyState() itd. To jest poprawne?

Tak, to powinno być w innej klasie. Ale niekoniecznie powinieneś zmuszać użytkownika do dziedziczenia po obu klasach. To brzmi jak zastosowanie dla kompozycji.

Dziedzicznie miałoby sens, jakby to użytkownik definiował zachowanie, które ma uruchomić framework. Jeżeli to framework dostarcza konkretną funkcjonalność, to nie ma konieczności dziedziczenia po klasie zapewniającej ową funkcjonalność, aby tą funkcjonalność uruchomić, a wręcz jest to błąd projektowy.

Przykładowe, jeżeli developer-użytkownik-frameworku ma mieć możliwość odtworzenia dzwięku, to może stworzyć sobie instancję klasy Core.Sound i uruchomić odpowiednie metody. Po co tutaj dziedziczenie?

PS. zakup i przeczytaj https://helion.pl/ksiazki/czysty-kod-podrecznik-dobrego-programisty-robert-c-martin,czykov.htm#format/d

edytowany 5x, ostatnio: nalik
Zobacz pozostały 1 komentarz
nalik
interfejs nie dostarcza funkcjonalności. interfejs definiuje kontrakt, który jakaś klasa ma spełnić.
nalik
Co do wskazówek, na początku niezbyt jasno zdefiniowałeś problem, a chyba nikomu nie chciało się bawić w analityka jak dzisiaj mi, dlatego odpowiedzi niekoniecznie rozwiązują Twój problem.
CR
Nie znam fachowego języka, więc nie potrafię w 100% przekazać tego tak, żeby ludzie z branży rozumieli co mam na myśli, ale staram się :).
nalik
No cóż, język jest istony. Zacznij od podstaw organizacji kodu. Dałem linka do odpowiedninej książki. Co prawda przykłady są Javie, ale to nie powinno stanowić problemu.
CR
Tak czy inaczej, dziękuję za pomoc.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)