Copy Control and BST

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);
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
Registered users can post here. Sign in or register to post.