Search code examples
c++g++forward-declaration

C++ forward declaration and recursive includes


I have a 8 files FileSystem.cpp, Node.cpp, Directory.cpp and File.cpp and their header hpp files.

this is basic model of in memory file system. class FileSystem is responsible for creating root Directory which has its own nodes aka files or directories.

The class Node is parent class for both Directory and File. I need to include Node into both files to inherit from, but inside Node class implementation I need to access the File and Directory to do some operations. If I try to make foward decleration, I will got undefined refernces of class members. I read some articles about this, but I can't solve my problem with headers. What is the right approach for solving this kind of problem in c++. How/should I use header to solve this kind of problem?

Please note my background is different, so I think have missed something on c++ about headers and recursive declaration.

Here the source of these files.

FileSystem.cpp

#include <string>
#include <vector>
#include <iostream>

using namespace std;

#include "Node.hpp"
#include "Directory.hpp"

class FileSystem
{
private:
    // methods

    // attributes
    string name;
    Directory *root;

    friend class Directory;

public:
    int get_fresh_uid()
    {
        return 10;
    }

    FileSystem(string in_name)
    {
        name = in_name;
        root = new Directory(this, get_fresh_uid(), "root", nullptr);
    }

    ~FileSystem() {
        
    }

    // accessors
    string get_name()
    {
        return name;
    }

    Directory *get_root()
    {
        return root;
    }

    friend ostream &operator<<(ostream &output, FileSystem &fs)
    {
        return output;
    }
};

Node.cpp

#include <string>

#include "Directory.cpp"
#include "File.cpp"
class FileSystem;

using namespace std;

class Node
{
protected:
    FileSystem *fs;
    int uid;
    string name;
    Directory *parent;
    Node(FileSystem *fs_in, int uid_in, string name_in, Directory *parent_in)
    {
            fs = fs_in;
            name = name_in,
            uid = uid_in;
            parent = parent_in;
    }

    virtual void print_to(ostream os, int num) = 0;

public:
    ~Node() {
        
    }

    // accessors
    string get_name()
    {
        return name;
    }

    // methods
    virtual bool is_directory() = 0;
    virtual Directory *to_directory() = 0;
    virtual File *to_file() = 0;
    virtual int size() = 0;
};

File.cpp

#include "Node.hpp"
#include "Directory.hpp"
#include "FileSystem.hpp"

class File : public Node
{
private:
    string content;
    ~File() {
        
    }

public:
    File(FileSystem *fs_in, int uid_in, string name_in, Directory *parent_in);

    // accessors
    void set_content(const string &value)
    {
        content = value;
    }
    // mutators
    string get_content()
    {
        return content;
    }
    // methods
    int size()
    {
        return content.size();
    }

    bool is_directory()
    {
        return false;
    }

    Directory *to_directory()
    {
        return nullptr;
    }

    File *to_file()
    {
        return this;
    }

    void print_to(ostream os, int num)
    {
        // os << "+ file: " << name << ", uid: " << uid << ", size: " << size << ", " << "content: " << content;
    }
};
 

Directory.cpp

#include "File.hpp"
#include "FileSystem.hpp"
#include "Node.hpp"

class Directory : public Node
{
private:
    Directory(FileSystem *fs_in, int uid_in, string name_in, Directory *parent_in);
    ~Directory();

    // attributes
    vector<Node *> children;
    
    // methods
    bool child_exists(string name)
    {
        for (int i = 0; i < children.size(); i++)
        {
            return children[i]->get_name() == name;
        }
    }

    friend class FileSystem;
public:
    // accessors
    string get_name() {
        return name;
    }
    
    //methods
    int size()
    {
        int sum;

        for (int i = 0; i < children.size(); i++)
        {
            Node *child = children[i];
            sum += child->size();
        }

        return sum;
    }

    bool is_directory()
    {
        return true;
    }

    Directory *to_directory()
    {
        return this;
    }

    File *to_file()
    {
        return nullptr;
    }

    File *add_file(string filename)
    {
        // check whether same name child node exists
        if (child_exists(filename))
            return nullptr;

        // create file
        File *new_file = new File(fs, fs->get_fresh_uid(), filename, this);
        // add file to the children vector
        children.push_back(new_file);
        return new_file;
    }

    Directory *add_directory(string dirname)
    {
        // check whether same name child node exists
        if (child_exists(dirname))
            return nullptr;

        // create file
        Directory *new_dir = new Directory(fs, fs->get_fresh_uid(), dirname, this);
        // add dir to the children vector
        children.push_back(new_dir);
        return new_dir;
    }

    bool remove_node(string name)
    {
        for (int i = 0; i < children.size(); i++)
        {
            if (children[i]->get_name() == name)
            {
                children.erase(children.begin() + i);
                return true;
            }
        }

        return false;
    }

    Node *find_node(string name)
    {
        for (int i = 0; i < children.size(); i++)
        {
            if (children[i]->get_name() == name)
                return children[i];
        }

        return nullptr;
    }

    void print_to(ostream os, int num)
    {
        // os << "+ directory: " << name << ", uid: " << uid << ", size: " << size;
    }

    friend ostream &operator<<(ostream &output, Directory &dir)
    {
        return output;
    }
};

Directory::~Directory()
{
    for (int i = 0; i < children.size(); i++)
    {
        delete children[i];
    }
}

Solution

  • #include "Directory.cpp"
    #include "File.cpp"
    

    I recommend not using source files as headers. Conventionally source files are conventionally compiled but if one of those included files is compiled and linked with the "Node.cpp" file, then your program is ill-formed due to One Definition Rule violations.

    If I try to make foward decleration, I will got undefined refernces of class members.

    A forward declaration should never cause "undefined references of class members". You may have misunderstood something.

    What is the right approach for solving this kind of problem in c++.

    Simply, you need to sort the definitions in an order where depended definitions are before those that depend on them. If and only if such sorting doesn't exist, then there is a cycle in the dependency graph, and problem cannot be fixed by re-ordering the definitions. In such case the design would have to be changed.

    The class Node is parent class for both Directory and File. I need to include Node into both files to inherit from, but inside Node class implementation I need to access the File and Directory

    This sounds like bad design. It may be possible to implement, but that doesn't mean that the design is necessarily good.

    That said, the following order works:

    • define Node
    • define Directory and File
    • define the function that uses Directory and File