Copy Control and BST

closed account (ywbpSL3A)
I've just started the Copy Control chapter in C++ Primer (5th Edition) and stumbled upon a vague exercise, which is as follows:

Given the following classes, implement a default constructor and the necessary copy-control members.

1
2
3
4
5
6
7
8
9
10
11
12
class TreeNode {
    private:
        std::string value;
        int count;
        TreeNode* left;
        TreeNode* right;
};

class BinStrTree {
    private:
        TreeNode* root;
};


The default constructor is simple but I don't quite grasp the use of copy-control members here, so I have a few questions:

1. What is the use of count? It doesn't make sense to use it as a reference count, since it's an independent data member.
2. What happens when a destructor gets called on TreeNode? value gets its' own destructor called, count is of a built-in data type so it's left alone, but what about left and right? Seeing as this is a binary search tree (not too familiar with those), shouldn't the other members of said tree be left untouched or simply reconnected rather than deleted?
3. The copy-constructor is relatively straightforward: copy over all members. But that doesn't make sense either. If I copy over left and/or right, how should the program differentiate between the two TreeNodes? Should this even support a copy-constructor and a copy-assignment operator?
Last edited on
> 1. What is the use of count? It doesn't make sense to use it as a reference count,
> since it's an independent data member.
In the context of your question "implement a default constructor and the necessary copy-control members", does it matter?

It's just something to copy.

> 2. What happens when a destructor gets called on TreeNode?
You just do
delete left;
delete right;


> how should the program differentiate between the two TreeNodes?
It's just another recursive function.
left = clonetree(other.left);
right = clonetree(other.right);
closed account (ywbpSL3A)
I guess that clears things up. Is the below implementation/design somewhat correct in case of this exercise?

treenode.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <string>

class TreeNode {
    public:
        TreeNode( const int c, const std::string& v = std::string() ) : value( v ), count( c ),
            left( nullptr ), right( nullptr ) {}
        TreeNode( const TreeNode& );
        ~TreeNode();
        
        TreeNode& operator=( const TreeNode& );
    
    private:
        std::string value;
        int count;
        TreeNode* left;
        TreeNode* right;
};


treenode.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include "treenode.h"

TreeNode::TreeNode( const TreeNode& rhs ) {
    value = rhs.value;
    count = rhs.count;
    left  = ( !rhs.left ) ? nullptr : ( new TreeNode( *rhs.left ) );
    right = ( !rhs.right ) ? nullptr : ( new TreeNode( *rhs.right ) );
}

TreeNode::~TreeNode() {
    delete left;
    delete right;
}

TreeNode& TreeNode::operator=( const TreeNode& rhs ) {
    TreeNode temp = rhs;
    
    delete left;
    delete right;
    
    value   = rhs.value;
    count   = rhs.count;
    left    = ( !temp.left ) ? nullptr : ( new TreeNode( *temp.left ) );
    right   = ( !temp.right ) ? nullptr : ( new TreeNode( *temp.right ) );
    
    return *this;
}


Everything seems to function as expected (all the members of a certain branch get copied until a nullptr is encountered). Although I did a bit further testing by adding the methods provided below to the TreeNode class and calling them from main.cpp.

treenode.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <string>
#include <iostream>

class TreeNode {
    public:
        TreeNode( const int c, const std::string& v = std::string() ) : value( v ), count( c ),
            left( nullptr ), right( nullptr ) {}
        TreeNode( const TreeNode& );
        ~TreeNode();
        
        TreeNode& operator=( const TreeNode& );
        
        void add_left( const TreeNode& t ) {
            left = new TreeNode( t );
        }
        
        const std::string& get_val() {
            return value;
        }
        
        void print_left() {
            if ( !left ) {
                std::cerr << "Error: \"left\" member is null." << std::endl;
                return;
            }
            
            std::cout << "left = " << left << std::endl;
            std::cout << "left->get_val() = " << left->get_val() << std::endl;
        }
        
        void change_val( const std::string& str ) {
            value = str;
        }
        
        void change_left_val( const std::string& str ) {
            if ( !left ) {
                std::cerr << "Error: \"left\" member is null." << std::endl;
                return;
            }
            
            left->change_val( str );
        }
    
    private:
        std::string value;
        int count;
        TreeNode* left;
        TreeNode* right;
};


main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "treenode.h"
#include <iostream>
#include <string>

int main() {
    TreeNode base( 10, "test" );
    base.add_left( TreeNode( 13, "zzzz" ) );
    base.print_left();
    
    TreeNode temp = base;
    temp.print_left();
    
    temp.change_left_val( "lolas" );
    temp.print_left();
    
    base.print_left();
    
    return 0;
}


Which, on my machine, produced the following output:
left = 0x6c1e68
left->get_val() = zzzz
left = 0x6c1e98
left->get_val() = zzzz
left = 0x6c1e98
left->get_val() = lolas
left = 0x6c1e68
left->get_val() = zzzz

And this was a bit strange to me. Is this a compiler optimisation or am I missing something? The addresses are identical yet they refer to objects whose data members differ (value in this case)?
Last edited on
Topic archived. No new replies allowed.