Reputation: 488
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
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:
override
specifier when overriding - this will make sure that you're really overriding a base class method and not just declaring a new one;unique_ptr
, but this is highly not recommended, as you would have to free them yourself to avoid memory leaks;Upvotes: 4
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
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
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