Jon Doe
Jon Doe

Reputation: 167

How do I access a pointer's subclass's methods if those methods are unique to the subclass?

A part of my program has two possible cases: (1) if the user only gives 2 command line arguments, take input from standard input (cin) (2) if the user gives 3 command line arguments (the last one being a file name), take input from the file. In order to not be reusing the same code for for both options, I tried to use a pointer to a superclass of both cin and ifstream, istream for both means of input.

My issue is that on lines 5 and 22 of the code below, I'm trying to reference methods only available to the subclass ifstream (open and close). Based on my logic, if those methods are called the pointer has to be pointing to a type ifstream, but the program fails to compile because those methods aren't defined in the istream class.

Is there any way to get around this?

istream *currentStream;
    if (argc == 3) {
        // Handle optional file input
        currentStream = new ifstream(argv[2]);
        currentStream->open(argv[2]);
        if (currentStream->fail()) {
            cerr << "FILE COULD NOT BE OPENED\n";
            return 1;
        }
    } else {
        currentStream = &cin;
    }
    string myLine;
    // go line by line and translate it
    while (getline(*currentStream, myLine)) {
        if (currentStream->eof()) {
            break;
        }
        cout << rot13(myLine) << endl;
    }
    if (dynamic_cast<ifstream*>(currentStream)) {
        currentStream->close();
    }
    // handle pointer
    delete currentStream;
    currentStream = NULL;
    return 0;

Upvotes: 1

Views: 71

Answers (4)

jaypb
jaypb

Reputation: 1574

Tas has the right approach in the comments. You can't call the method directly on currentStream, you have to call it on the cast interface.

ifstream* stream = dynamic_cast<ifstream*>(currentStream);
if (stream) {
     stream->close();
}

I also think you should probably change your code to not rely on dynamic cast, either a new interface or separate method.

Upvotes: 0

Deduplicator
Deduplicator

Reputation: 45664

Simply extract a method:

void process(std::istream is) {
    string myLine;
    // go line by line and translate it
    while (getline(is, myLine))
        cout << rot13(myLine) << endl;
}

int main(int argc, char** argv) {
    if (argc == 3) {
        ifstream ifs(argv[2]);
        if (!ifs) {
            cerr << "FILE COULD NOT BE OPENED\n";
            return 1;
        }
        process(ifs);
    } else {
        process(cin);
    }
}

Upvotes: 0

JaMiT
JaMiT

Reputation: 16873

There are several places where your code could be improved. I think the most prominent place for improvement is that you are trying to do too much in one function. Your goal of not duplicating code is good, but modularize your approach. Move the shared code to its own function, as in:

void do_stuff(std::istream & currentStream)
{
    std::string myLine;
    // go line by line and translate it
    while (getline(currentStream, myLine)) {
        if (currentStream.eof()) {
            break;
        }
        std::cout << rot13(myLine) << std::endl;
    }
}

This function should contain everything that is shared between the two code paths. (I changed the pointer to a reference so that callers immediately know that a null pointer is not acceptable.) When you change your main function so that it calls this one, you should notice that a few things become easier. In particular, there is no need for dynamic allocation (which leads to no attempt to delete &cin – that looked bad). You can readily use a local variable for your file stream.

int main(int argc, const char ** argv)
{
    if (argc == 3) {
        // Handle optional file input
        std::ifstream fileStream(argv[2]);
        fileStream.open(argv[2]);
        if (fileStream.fail()) {
            std::cerr << "FILE COULD NOT BE OPENED\n";
            return 1;
        }
        do_stuff(fileStream);
        fileStream.close();
    } else {
        do_stuff(std::cin);
    }
    return 0;
}

By moving the common code to a separate function, you get to stay in your if clause. There is no need to deduce whether or not *currentStream needs closing, since you never left the code branch that created the file.


There is another place where you could simplify things. Don't call open and close. You are using the ifstream constructor that takes a file name, so the constructor already calls open for you. (When you explicitly call open, you are telling the computer to close the file and re-open it.) Similarly, the destructor will call close for you; that is a key point of RAII.

Getting rid of the unneeded calls leaves:

int main(int argc, const char ** argv)
{
    if (argc == 3) {
        // Handle optional file input
        std::ifstream fileStream(argv[2]);
        if (fileStream.fail()) {
            std::cerr << "FILE COULD NOT BE OPENED\n";
            return 1;
        }
        do_stuff(fileStream);
        // Keep in mind that, even though there is no C++ code here, there is something
        // important being done after the call to do_stuff. Specifically, the destructor
        // for fileStream is called, which closes the file for you.
    } else {
        do_stuff(std::cin);
    }
    return 0;
}

Upvotes: 1

David G
David G

Reputation: 96810

Dynamically-allocate a "copy" of std::cin by grabbing its buffer. Storing the memory in a std::unique_ptr would also be ideal as you wouldn't have to worry about manually deleting the pointer.

#include <memory>

int main(int argc, char* argv[]) {
  std::unique_ptr<std::istream> currentStream( argc == 3
    ? std::make_unique<std::ifstream>(argv[2])
    : std::make_unique<std::istream>(std::cin.rdbuf())
  );

  // will only fail when the file cannot open
  if (!currentStream) {
    std::cerr << "FILE COULD NOT BE OPENED\n";
    return 1;
  }

  std::string myLine;
  // go line by line and translate it
  while (std::getline(*currentStream, myLine)) {
    std::cout << rot13(myLine) << std::endl;
  }
}

Upvotes: 3

Related Questions