Как получить доступ к методам подкласса указателя, если эти методы уникальны для подкласса?

Часть моей программы имеет два возможных случая: (1) если пользователь дает только 2 аргумента командной строки, принимает ввод со стандартного ввода (cin) (2) если пользователь дает 3 аргумента командной строки (последний из которых является именем файла ), получить ввод из файла. Чтобы не использовать повторно один и тот же код для обоих вариантов, я попытался использовать указатель на суперкласс как cin, так и ifstream, istream для обоих средств ввода.

Моя проблема в том, что в строках 5 и 22 кода ниже я пытаюсь сослаться на методы, доступные только для подкласса ifstream (открыть и закрыть). Исходя из моей логики, если вызываются эти методы, указатель должен указывать на тип ifstream, но программа не компилируется, потому что эти методы не определены в классе istream.

Есть ли способ обойти это?

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;

person Jon Doe    schedule 25.09.2019    source источник
comment
Вы, вероятно, хотели бы обернуть их в отдельные классы, где вы могли бы вызывать open и close, а класс без ifstream просто ничего не делал бы, а другой класс фактически открывал и закрывал бы ifstream. У вас правильная идея с dynamic_cast, но вы должны получить возврат, подобный if (ifstream* filestream = dynamic_cast...) { filestream->close()   -  person Tas    schedule 26.09.2019
comment
Я начал писать ответ, но потом понял, что те строчки, которые вызывают у вас вопросы: не нужны. Если вы передадите значение конструктору std::ifstream, он сам вызовет open. И вызов close не нужен, потому что его вызовет деструктор std::ifstream. Это оставляет одну истинную проблему: delete currentStream; - это неопределенное поведение, когда argc != 3 из-за попытки delete &cin.   -  person Algirdas Preidžius    schedule 26.09.2019


Ответы (4)


Динамически выделяйте «копию» std::cin, захватывая ее буфер. Хранение памяти в std::unique_ptr также было бы идеальным, поскольку вам не нужно было бы беспокоиться об удалении указателя вручную.

#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;
  }
}
person 0x499602D2    schedule 25.09.2019

Есть несколько мест, где ваш код можно улучшить. Я думаю, что наиболее заметным местом для улучшения является то, что вы пытаетесь сделать слишком много в одной функции. Ваша цель не дублировать код — это хорошо, но модульность вашего подхода. Переместите общий код в его собственную функцию, например:

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;
    }
}

Эта функция должна содержать все, что является общим для двух путей кода. (Я заменил указатель на ссылку, чтобы вызывающие сразу знали, что нулевой указатель неприемлем.) Когда вы изменяете основную функцию так, чтобы она вызывала эту, вы должны заметить, что некоторые вещи становятся проще. В частности, нет необходимости в динамическом размещении (что приводит к отсутствию попытки delete &cin выглядеть плохо). Вы можете легко использовать локальную переменную для вашего файлового потока.

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;
}

Переместив общий код в отдельную функцию, вы останетесь в своем предложении if. Нет необходимости делать вывод, нужно ли закрывать *currentStream, поскольку вы никогда не покидали ветку кода, которая создала файл.


Есть еще одно место, где вы могли бы упростить вещи. Не звоните open и close. Вы используете конструктор ifstream, который принимает имя файла, поэтому конструктор уже вызывает open за вас. (Когда вы явно вызываете open, вы говорите компьютеру закрыть файл и повторно открыть его.) Точно так же деструктор вызовет close вместо вас; это ключевой момент RAII.

Избавление от ненужных звонков оставляет:

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;
}
person JaMiT    schedule 25.09.2019

Просто извлеките метод:

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);
    }
}
person Deduplicator    schedule 25.09.2019

У Таса правильный подход в комментариях. Вы не можете вызвать метод непосредственно в currentStream, вы должны вызвать его в интерфейсе приведения.

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

Я также думаю, что вам, вероятно, следует изменить свой код, чтобы он не полагался на динамическое приведение, будь то новый интерфейс или отдельный метод.

person jaypb    schedule 25.09.2019
comment
1) Но даже не нужно вызывать close, если кто-то delete обрабатывает выделенный поток - его вызовет деструктор std::ifstream. 2) Технически спрашивающий спрашивал и о строке currentStream->open(argv[2]); (которая не адресована в этом ответе), но она тоже лишняя, т.к. используется конструктор. 3) В этом ответе не упоминается возможный UB delete currentStream;. - person Algirdas Preidžius; 26.09.2019
comment
Я думаю, вы имели в виду поток вместо ifstream в вашем условии if и в строке, которая вызывает close? - person JaMiT; 26.09.2019