user3736114
user3736114

Reputation: 488

Polymorphism in C++. Calling inherited methods on base class

I am trying to implement a simple file system. I have a base class entry. I have two classes that inherit from entry called File and Directory. Directory contains a list of Entry objects. I have a method called changeDir for Directory and File but not Entry. I was hoping I could call this method on Entry objects and the compiler would know which method to use based on whether the Entry was a Directory or File. Any way to accomplish this?

class Entry{
  public:
    std::chrono::time_point<std::chrono::system_clock> dateCreated;
    std::string fileName;
    Entry(std::string name);
    ~Entry();
};

Directory* Directory::changeDir(Directory* dir, Directory* currentDir){
  currentDir = dir;
  return currentDir;
}

void File::changeDir(Directory* dir, Directory* currentDir){
  std::cout<<"You cannot change directories into a file\n";
}

unordered_set<Entry*> s;
s.add(File);
s.add(Directory);
for each in s: each.changeDir(); //pseudo code

Unfortunately it tells me a changeDir() method has not been declared for Entry class. I would normally just make a method for the Entry class but it says Directory is not declared in this scope (obviously, since Directory is a derived class of Entry). I feel like I've done this type of polymorphism in python before. Is there no way to do in C++? Thanks.

Edit: Set takes pointers to Entry objects, not actual Entry objects.

Edit2: It seems that virtual classes is the solution to this specific issue. I believe the better and more robust solution will be to have each directory contain a set of files and a set of directorys instead of one set of both.

Upvotes: 1

Views: 122

Answers (4)

Tarc
Tarc

Reputation: 3312

You need virtual methods:

struct Entry
{
    virtual void changeDir( )
    {
        std::cout << "Entry" << std::endl;
    }
};

struct File : Entry
{
    virtual void changeDir( ) override
    {
        std::cout << "File" << std::endl;
    }
};

struct Directory : Entry
{
    virtual void changeDir( ) override
    {
        std::cout << "Directory" << std::endl;
    }
};

But then, if your collection is of plain Entry objects, slicing will spoil all your hard work:

int main()
{
    std::vector< Entry > entries;

    entries.push_back( File { } );
    entries.push_back( Directory { } );

    entries[ 0 ].changeDir( ); // > Entry
    entries[ 1 ].changeDir( ); // > Entry

In order to hold the right objects, you have to allocate them dynamically and store some kind of pointer:

    std::vector< std::unique_ptr< Entry > > entryPointers;

    entryPointers.push_back( std::make_unique< File >( ) );
    entryPointers.push_back( std::make_unique< Directory >( ) );

    entryPointers[ 0 ]->changeDir( ); // > File 
    entryPointers[ 1 ]->changeDir( ); // > Directory
}

Some points:

  • Don't forget the override specifier when overriding - this will make sure that you're really overriding a base class method and not just declaring a new one;
  • You could possibly use raw pointers instead of unique_ptr, but this is highly not recommended, as you would have to free them yourself to avoid memory leaks;
  • Although this works, it's advisable to avoid virtual methods in your interfaces when you can. See for instance this fundamental Herb Sutter's article.

Upvotes: 4

Adam Bac
Adam Bac

Reputation: 11

I think that if you want to call changeDir on the whole set, you should be sure that all the elements are instances of Directory (since it only make sense for them), and declare the set as containting Directories, not Entries.

On the other hand if your set may contain both type of Entries, you shouldn't try to call a possibly undefined method on them in the first place.

Upvotes: 1

happydave
happydave

Reputation: 7187

As long as you only refer to pointers or references to Directory, you can just forward-declare it:

class Directory;
class Entry {
  ...
  virtual Directory* changeDir(Directory* dir, Directory* currentDir) = 0;
};

Upvotes: 1

user2736738
user2736738

Reputation: 30926

You can make the method changedir a pure virtual function and then you can achieve the required behavior by instantiating a Base class object's pointer or reference (which will either point to file or directory) and then call the function and by polymorphism it will be called properly. [Object slicing]

Upvotes: 1

Related Questions