Builder pattern - bład kompilacji

Builder pattern - bład kompilacji
ReallyGrid
  • Rejestracja:około 11 lat
  • Ostatnio:21 dni
0

Napisałem dla siebie implementację wzorca Builder rozkładając go na dwa pliki. Napisałem go w ten sposób (tylko jedna metoda, żeby nie zaciemniać):
builder.h:

Kopiuj
#ifndef BUILDER_H
#define BUILDER_H

#include "shape.h"
//class Shape;

class Builder {
public:
    Builder& setSides(int sides) {
        this->sides = sides;
        return *this;
    }

    int getSides() const {
        return sides;
    }

    Shape* build() {
        return new Shape(*this);
    }

private:
    int sides;
};

#endif

shape.h:

Kopiuj
#ifndef SHAPE_H
#define SHAPE_H

//#include "builder.h"
class Builder;

class Shape {
public:
    Shape(const Builder& builder): sides(builder.getSides()) {}

    int getSides() const {
        return sides;
    }

private:
    int sides;
};

#endif

W klasie Builder mam odwołanie do klasy Shape, ale i w klasie Shape mam odwołanie do klasy Builder. Gdy mam tak jak teraz czyli w pliku builder.h jest include, a w pliku shape.h jest forward declaration, to mam takie komunikaty błędów:

Kopiuj
In file included from builder.h:4:0,
                 from main.cpp:1:
shape.h: In constructor ‘Shape::Shape(const Builder&)’:
shape.h:9:42: error: invalid use of incomplete type ‘const class Builder’
     Shape(const Builder& builder): sides(builder.getSides()) {}
                                          ^~~~~~~
shape.h:5:7: note: forward declaration of ‘class Builder’
 class Builder;
       ^~~~~~~

A gdy jest na odwrót (w pliku builder.h jest forward declaration, a w pliku shape.h jest include) to mam takie:

Kopiuj
In file included from main.cpp:1:0:
builder.h: In member function ‘Shape* Builder::build()’:
builder.h:19:31: error: invalid use of incomplete type ‘class Shape’
         return new Shape(*this);
                               ^
builder.h:5:7: note: forward declaration of ‘class Shape’
 class Shape;
       ^~~~~

Nie wiem już co robić. Oczywiście jak wrzucę te klasy do jednego pliku to jest ok ale specjalnie chcę je mieć oddzielnie. Jak to zrobić żeby działało? Próbowałem już chyba każdej kombinacji includów i forward declaration. Najlepsze jest to, że w necie pełno jest odpowiedzi ale wszystkie są napisane dla jednego pliku. Tak to mnie też działa ale nie o to mi chodzi.
Całość wywołuję jako:

Kopiuj
Builder builder;
Shape* shape = builder.setSides(4).build();
ReallyGrid
  • Rejestracja:około 11 lat
  • Ostatnio:21 dni
0

Mały edit:
Przeniosłem definicje do odrębnych plików:
builder.h:

Kopiuj
#ifndef BUILDER_H
#define BUILDER_H

#include "shape.h"
//class Shape;

class Builder {
public:
    Builder& setSides(int sides);

    int getSides() const;

    Shape* build();

private:
    int sides;
};

#endif

builder.cpp:

Kopiuj
#include "builder.h"

Builder& Builder::setSides(int sides) {
    this->sides = sides;
    return *this;
}

int Builder::getSides() const {
    return sides;
}

Shape* Builder::build() {
    return new Shape(*this);
}

shape.h:

Kopiuj
#ifndef SHAPE_H
#define SHAPE_H

//#include "builder.h"
class Builder;

class Shape {
public:
    Shape(const Builder& builder);

    int getSides() const;

private:
    int sides;
};

#endif

shape.cpp:

Kopiuj
#include "shape.h"

Shape::Shape(const Builder& builder): sides(builder.getSides()) {}

int Shape::getSides const {
    return sides;
}

I teraz bez względu w którym pliku jest include, a w której forward declaration otrzymuję ten sam błąd ale tym razem błąd pochodzący od linkera:

Kopiuj
/tmp/ccog0SRQ.o:main.cpp:(.text+0x1a): undefined reference to `Builder::setSides(int)'
/tmp/ccog0SRQ.o:main.cpp:(.text+0x1a): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `Builder::setSides(int)'
/tmp/ccog0SRQ.o:main.cpp:(.text+0x22): undefined reference to `Builder::build()'
/tmp/ccog0SRQ.o:main.cpp:(.text+0x22): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `Builder::build()'
collect2: error: ld returned 1 exit status

Podsumowując, nadal nie działa :/

edytowany 2x, ostatnio: ReallyGrid
twonek
  • Rejestracja:ponad 10 lat
  • Ostatnio:prawie 2 lata
  • Postów:2500
1

Forward declaration to nie magia, jest to komunikat dla kompilatora "masz tu klasę o tej nazwie, na razie nie musisz znać jej bebechów, ale podam je gdy będą potrzebne". No ale jeśli już w pliku nagłówkowym chcesz użyć tej klasy, na przykład tu:

Kopiuj
Shape(const Builder& builder): sides(builder.getSides()) {}

to wtedy kompilator protestuje "ale ja muszę znać bebechy klasy Builder już teraz by móc stwierdzić, czy ona ma metodę getSides().
Podobna sytuacja jest z

Kopiuj
return new Shape(*this);

by stworzyć obiekt trzeba znać dokładnie implementację klasy (żeby chociaż wiedzieć ile pamięci zarezerwować).

Czyli jeśli masz forward declaration to możesz jedynie zadeklarować wskaźnik lub referencję do danej klasy.
W Twoim konkretnym przypadku łatwiej naprawić poprzez

Kopiuj
Shape* build();

a potem implementację wrzucić do builder.cpp.

Edit: w Twoim poprawionym przykładzie brakuje #include "builder.h" w shape.cpp, przez co klasa Shape nigdy nie ma dostępu do implementacji klasy Builder.

edytowany 1x, ostatnio: twonek
ReallyGrid
Czyli.... moment. Zakładając, że oddziela się deklaracje od definicji na pliki .h i .cpp, to FD stosuje się wyłącznie w plikach nagłówkowych, a inkludy w plikach .cpp. Dobrze Cię rozumiem?
ReallyGrid
  • Rejestracja:około 11 lat
  • Ostatnio:21 dni
0

Ok, kolejna poprawka:
builder.h:

Kopiuj
#ifndef BUILDER_H
#define BUILDER_H

class Shape;     // bo w klasie Builder mam deklarację metody:  Shape* build();

class Builder {
public:
    // tutaj bez zmian
};

#endif

builder.cpp:

Kopiuj
#include "builder.h"
#include "shape.h"     // bo mam definicję metody: Shape* Builder::build() { return new Shape(*this); }

// definicje metod bez zmian

shape.h:

Kopiuj
#ifndef SHAPE_H
#define SHAPE_H

class Builder;      // bo mam deklarację konstruktora: Shape(const Builder& builder);

class Shape {
public:
    // tutaj również bez zmian
};

#endif

shape.cpp:

Kopiuj
#include "shape.h"
#include "builder.h"      // bo mam tutaj taki a nie inny konstruktor, patrz niżej

Shape::Shape(const Builder& builder): sides(builder.getSides()) {}

int Shape::getSides const {
    return sides;
}

No i po próbie kompilacji cały czas mam ten sam błąd:

Kopiuj
/tmp/ccVgXyI9.o:main.cpp:(.text+0x1a): undefined reference to `Builder::setSides(int)'
/tmp/ccVgXyI9.o:main.cpp:(.text+0x1a): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `Builder::setSides(int)'
/tmp/ccVgXyI9.o:main.cpp:(.text+0x22): undefined reference to `Builder::build()'
/tmp/ccVgXyI9.o:main.cpp:(.text+0x22): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `Builder::build()'
collect2: error: ld returned 1 exit status
edytowany 2x, ostatnio: ReallyGrid
twonek
  • Rejestracja:ponad 10 lat
  • Ostatnio:prawie 2 lata
  • Postów:2500
1

Jesteś pewien, że budujesz nową wersję kodu? Mnie się kompiluje bez problemu, oczywiście po poprawieniu

Kopiuj
int Shape::getSides() const {
ReallyGrid
  • Rejestracja:około 11 lat
  • Ostatnio:21 dni
0

Wczoraj już poszedłem spać. Dzisiaj poprawiłem tę linijkę. Jak mogłem nie zauważyć brak nawiasów ;) Ale niestety nadal mam to samo. Wklejam cały kod. Zerknij proszę gdzie mam różnice iędzy Tw kodem a moim. Ewentualnie wklej cały swój kod bo czasem czegoś i tak się nie zauważy, zwłaszcza jak się coś porównuje. No to startuję… a swoją drogą zastanawiam się czy dać const dla metody Shape* Builder::build() {. W końcu ona tworzy nowy obiekt Shape i nie zmienia stanu obiektu Builder.
builder.h:

Kopiuj
#ifndef BUILDER_H
#define BUILDER_H

class Shape;

class Builder {
public:
    Builder& setSides(int sides);
    int getSides() const;
    Shape* build(); // mozna dac ja jako const (tak mi sie wydaje)

private:
    int sides;
};

#endif

builder.cpp:

Kopiuj
#include "builder.h"
#include "shape.h"

Builder& Builder::setSides(int sides) {
    this->sides = sides;
    return *this;
}

int Builder::getSides() const {
    return sides;
}

Shape* Builder::build() {
    return new Shape(*this);
}

shape.h:

Kopiuj
#ifndef SHAPE_H
#define SHAPE_H

class Builder;

class Shape {
public:
    Shape(const Builder& builder);
    int getSides() const;

private:
    int sides;
};

#endif

shape.cpp:

Kopiuj
#include "shape.h"
#include "builder.h"

Shape::Shape(const Builder& builder): sides(builder.getSides()) {}

int Shape::getSides() const {
    return sides;
}

main.cpp:

Kopiuj
#include "shape.h"
#include "builder.h"

int main() {
    Builder builder;
    Shape* shape = builder.setSides(4).build();
    delete shape;
    return 0;
}
ReallyGrid
  • Rejestracja:około 11 lat
  • Ostatnio:21 dni
0

Wróć działa. Błąd leżał gdzie indziej :P
Kompilowałem to jako
g++ main.cpp -std=c++11
zapomniałem dodać dwa pliki :D hehe. Teraz gdy je dopiszę:
g++ main.cpp builder.cpp shape.cpp -std=c++11
Wszystko jest w porządku. Do śmierci bym się z tym męczył

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)