Builder pattern - bład kompilacji

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:

#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:

#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:

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:

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:

Builder builder;
Shape* shape = builder.setSides(4).build();
0

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

#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:

#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:

#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:

#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:

/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 :/

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:

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

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

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.

0

Ok, kolejna poprawka:
builder.h:

#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:

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

// definicje metod bez zmian

shape.h:

#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:

#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:

/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
1

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

int Shape::getSides() const {
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:

#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:

#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:

#ifndef SHAPE_H
#define SHAPE_H

class Builder;

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

private:
    int sides;
};

#endif

shape.cpp:

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

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

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

main.cpp:

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

int main() {
    Builder builder;
    Shape* shape = builder.setSides(4).build();
    delete shape;
    return 0;
}
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ł

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