1

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.

user3736114
  • 458
  • 1
  • 6
  • 17
  • If you put `changeDir` at the base class, you *could* do this, but since that does not pertain to `File` why have it? Also, without references or pointers, you will get [object slicing](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=3&cad=rja&uact=8&ved=0ahUKEwinvqq60OPUAhXM54MKHedcAdQQFgg0MAI&url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FObject_slicing&usg=AFQjCNHH_00F7lDRk2r0g2QEtrWX0SJ9wg) – crashmstr Jun 29 '17 at 17:57
  • Also, if `File` and `Directory` are classes, you can't add *the class* to your set. – crashmstr Jun 29 '17 at 17:58
  • @crashmstr The method will get called on File objects sometimes and I need a way to handle that scenario. I can't put the method in the base class since it returns Directory* and takes Directory* as args. Compiler states Directory is not declared in that scope. I'm not sure what you mean by the other comment. Do you mean my set will only take Entry objects? Aren't Directory and File technically Entry objects? – user3736114 Jun 29 '17 at 18:04
  • If you have `class Directory`, then `Directory` is a type, a type cannot be added to a set, an instance of a type can be added to a set. i.e. `s.add(double)` is not going to work. – crashmstr Jun 29 '17 at 18:05
  • Can you have the calling code *test* to see if it is a `Directory` (`dynamic_cast` on a pointer)? – crashmstr Jun 29 '17 at 18:06
  • Oh, thats pseudo code. That's not what I literally will be doing. I will be adding instances of Files and Directory to the set. Sorry for the confusion. – user3736114 Jun 29 '17 at 18:18
  • And that is the problem with showing fake code: you need to have it real enough so that we don't "fix" problems in your fake code instead of helping to fix the real problem. – crashmstr Jun 29 '17 at 20:14

4 Answers4

4

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.
Tarc
  • 3,214
  • 3
  • 29
  • 41
1

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]

user2736738
  • 30,591
  • 5
  • 42
  • 56
1

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;
};
happydave
  • 7,127
  • 1
  • 26
  • 25
1

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.

Adam Bac
  • 11
  • 4