Używam idiomu pimpl z std::unique_ptr
:
class window {
window(const rectangle& rect);
private:
class window_impl; // defined elsewhere
std::unique_ptr<window_impl> impl_; // won't compile
};
Jednak pojawia się błąd kompilacji dotyczący użycia niekompletnego typu w wierszu 304 w <memory>
:
Nieprawidłowe zastosowanie „
sizeof
” do niekompletnego typuuixx::window::window_impl
”
O ile mi wiadomo, std::unique_ptr
powinno być możliwe użycie niekompletnego typu. Czy to błąd w libc ++, czy robię tu coś złego?
Odpowiedzi:
Oto kilka przykładów
std::unique_ptr
niepełnych typów. Problem polega na zniszczeniu.Jeśli używasz pimpl z
unique_ptr
, musisz zadeklarować destruktor:ponieważ w przeciwnym razie kompilator generuje domyślny i potrzebuje do tego pełnej deklaracji
foo::impl
.Jeśli masz konstruktory szablonów, to wkręcasz się, nawet jeśli nie konstruujesz
impl_
elementu:W zakresie przestrzeni nazw użycie
unique_ptr
nie będzie działać:ponieważ kompilator musi tutaj wiedzieć, jak zniszczyć ten statyczny obiekt o czasie trwania. Obejście to:
źródło
foo::~foo() = default;
w pliku srcJak wspomniał Alexandre C. , problem sprowadza się do
window
domyślnego zdefiniowania destruktora w miejscach, w których typwindow_impl
wciąż jest niekompletny. Oprócz jego rozwiązań, innym obejściem, którego użyłem, jest zadeklarowanie funktora Deletera w nagłówku:Zauważ, że użycie niestandardowej funkcji Deletera wyklucza użycie
std::make_unique
(dostępnego w C ++ 14), jak już tutaj omówiono .źródło
użyj niestandardowego narzędzia do usuwania
Problem polega na tym, że
unique_ptr<T>
musi on wywoływać destruktorT::~T()
we własnym destruktorze, jego operatorze przypisania ruchu iunique_ptr::reset()
funkcji elementu (tylko). Jednak muszą być wywoływane (niejawnie lub jawnie) w kilku sytuacjach PIMPL (już w zewnętrznym instrumencie niszczącym klasy i operatorze przypisania ruchu).Jak już wskazano w innej odpowiedzi, jeden sposób, aby uniknąć sytuacji jest przeniesienie wszystkich operacji, które wymagają
unique_ptr::~unique_ptr()
,unique_ptr::operator=(unique_ptr&&)
iunique_ptr::reset()
do pliku źródłowego, gdzie klasa pimpl pomocnik jest właściwie zdefiniowane.Jest to jednak dość niewygodne i do pewnego stopnia przeczy samemu sensowi idiomu pimpl. Znacznie bardziej przejrzyste rozwiązanie, które pozwala uniknąć korzystania z niestandardowego narzędzia do usuwania i przenosi jego definicję do pliku źródłowego, w którym mieszka klasa pomocnika pryszcza . Oto prosty przykład:
Zamiast oddzielnej klasy deletera możesz także użyć funkcji swobodnej lub
static
elementufoo
w połączeniu z lambda:źródło
Prawdopodobnie masz jakieś ciała funkcji w pliku .h w klasie, która używa niekompletnego typu.
Upewnij się, że w oknie .h dla okna klasy masz tylko deklarację funkcji. Wszystkie ciała funkcji dla okna muszą znajdować się w pliku .cpp. A także dla window_impl ...
Btw, musisz jawnie dodać deklarację destruktora dla klasy Windows w pliku .h.
Ale NIE MOŻESZ umieścić pustego ciała dtor w pliku nagłówka:
To musi być tylko deklaracja:
źródło
Aby dodać do odpowiedzi drugiego użytkownika na temat niestandardowego usuwacza, w naszej wewnętrznej „bibliotece narzędzi” dodałem nagłówek pomocnika w celu wdrożenia tego wspólnego wzorca (
std::unique_ptr
niepełnego typu, znanego tylko niektórym jednostkom językowym w celu np. Uniknięcia długich czasów kompilacji lub zapewnienia tylko nieprzezroczysty uchwyt dla klientów).Zapewnia wspólne rusztowanie dla tego wzorca: niestandardową klasę usuwania, która wywołuje zewnętrznie zdefiniowaną funkcję usuwania, alias typu dla
unique_ptr
tej klasy usuwania oraz makro do deklarowania funkcji usuwania w JT, która ma pełną definicję rodzaj. Myślę, że ma to pewną ogólną przydatność, więc oto:źródło
Może nie być najlepszym rozwiązaniem, ale czasami możesz zamiast tego użyć shared_ptr . Jeśli oczywiście jest to trochę przesada, ale ... jeśli chodzi o Unique_ptr, być może poczekam jeszcze 10 lat, aż twórcy standardu C ++ zdecydują się użyć lambdy jako deletera.
Inna strona. Według twojego kodu może się zdarzyć, że na etapie zniszczenia window_impl będzie niekompletny. Może to być przyczyną nieokreślonego zachowania. Zobacz: Dlaczego tak naprawdę usunięcie niekompletnego typu jest niezdefiniowanym zachowaniem?
Więc jeśli to możliwe, zdefiniowałbym bardzo podstawowy obiekt dla wszystkich twoich obiektów za pomocą wirtualnego destruktora. I jesteś prawie dobry. Należy pamiętać, że system wywoła wirtualny destruktor dla twojego wskaźnika, więc powinieneś zdefiniować go dla każdego przodka. Powinieneś również zdefiniować klasę podstawową w sekcji dziedziczenia jako wirtualną (zobacz to, aby uzyskać szczegółowe informacje).
źródło