Czy mam rację mówiąc, że muszę usunąć wskaźnik w destruktorze?
Podczas projektowania takiego obiektu należy najpierw odpowiedzieć na pytanie: Czy obiekt jest właścicielem pamięci wskazanej przez ten wskaźnik? Jeśli tak, to oczywiście destruktor obiektu musi wyczyścić pamięć, więc tak, musi wywołać delete. I wydaje się, że to twój zamiar z podanym kodem.
Jednak w niektórych przypadkach możesz chcieć mieć wskaźniki odnoszące się do innych obiektów, których żywotność ma być zarządzana przez coś innego. W takim przypadku nie chcesz wywoływać delete, ponieważ jest to obowiązkiem innej części programu, aby to zrobić. Co więcej, zmienia to cały następny projekt, który trafia do konstruktora kopiowania i operatora przypisania.
Będę kontynuować udzielanie odpowiedzi na pozostałe pytania, zakładając, że chcesz, aby każdy obiekt TreeNode posiadał prawa własności do lewego i prawego obiektu.
Czy to właściwy sposób na zastosowanie domyślnego konstruktora?
Nie trzeba zainicjować left
i right
wskaźniki NULL (lub 0, jeśli wolisz). Jest to konieczne, ponieważ niezainicjowane wskaźniki mogą mieć dowolną wartość. Jeśli twój kod zawsze domyślnie konstruuje TreeNode, a następnie niszczy go, nigdy nie przypisując niczego do tych wskaźników, wówczas usuwane będzie wywołanie niezależnie od wartości początkowej. Więc w tym projekcie, jeśli te wskaźniki nie wskazują na nic, musisz zagwarantować, że są ustawione na NULL.
Jak przypisać zmienną wskaźnika do konstruktora kopiowania? Sposób, w jaki napisałem w Copy Constructor może być błędny.
Linia left = new TreeNode();
tworzy nowy obiekt TreeNode i ustawia left
, aby wskazywało na niego. Linia left = node.left;
ponownie przypisuje wskaźnik do wskazania dowolnego obiektu TreeNode o numerze node.left
. Są z tym dwa problemy.
Problem 1: Nic teraz nie wskazuje na ten nowy TreeNode. Jest zagubiony i staje się przeciekiem pamięci, ponieważ nic nie może go zniszczyć.
Problem 2: Teraz zarówno left
, jak i node.left
wskazują na ten sam obiekt TreeNode. Oznacza to, że obiekt, który jest kopiowany, a obiekt, z którego pobiera wartości, będzie uważał, że jest właścicielem tego samego TreeNode i oba wywołają usuwanie na nim w swoich destruktorach. Wywołanie dwukrotnego usunięcia tego samego obiektu jest zawsze błędem i spowoduje problemy (w tym ewentualne awarie lub uszkodzenie pamięci).
Ponieważ każdy TreeNode ma swoje lewe i prawe węzły, prawdopodobnie najbardziej sensownym rozwiązaniem będzie wykonanie kopii. Więc można napisać coś podobnego do:
TreeNode::TreeNode(const TreeNode& node)
: left(NULL), right(NULL)
{
data = node.data;
if(node.left)
left = new TreeNode(*node.left);
if(node.right)
right = new TreeNode(*node.right);
}
Czy muszę wdrożyć ten sam kod (z wyjątkiem zwrotu) zarówno kopię konstruktora i operatior =?
Prawie na pewno. A przynajmniej kod każdego z nich powinien mieć taki sam efekt końcowy. Byłoby bardzo mylące, gdyby kopiowanie konstrukcji i przydział miały różne efekty.
EDYCJA - Powyższy akapit powinien być: Kod w każdym powinien mieć taki sam efekt końcowy, ponieważ dane są kopiowane z innego obiektu. Będzie to często obejmować bardzo podobny kod. Jednak operator przypisania prawdopodobnie musi sprawdzić, czy coś zostało już przypisane do left
i right
i tak wyczyścić je. W konsekwencji może również potrzebować uważać na własny przydział lub napisać w sposób, który nie spowoduje niczego złego w trakcie samozasadzenia.
W rzeczywistości istnieją sposoby implementacji jednego przy użyciu drugiego, tak aby rzeczywisty kod, który manipuluje zmiennymi składowymi, jest zapisywany tylko w jednym miejscu. Inne pytania na temat SO omówiły to, na przykład: this one.
To niepotrzebne przypisanie łańcucha pustego na '' data' w swoim konstruktorze TreeNode', ponieważ domyślny konstruktor 'std :: string' zrobi to samo. –
Konstruktor kopii i operator przypisania mają nieco różniące się semantyki kopiowania. To jest mylące dla użytkowników twojej klasy. – sbi
hi sbi, Masz na myśli, powinienem użyć dokładnej kopii tego samego kodu zarówno dla konstruktora kopii, jak i operatora? –