LINUX.ORG.RU

stdio vs iostream и куча вопросов

 ,


0

2

Здравствуйте, коллеги! Везде пишут, что если пишешь на С++ то нужно использовать iostream.

Но я не могу понять в чем преимущество потокового ввода-вывода?

На мой взгляд, stdio.h удобнее в использовании как для вывода в терминал, так и для файловых операций. Кстати, этот подход более соответствует философии Linux, «все есть файл».

Где-то писали, что в С++ можно использовать stdio (без «.h»), но я таковой у себя в Fedora 37 не обнаруживаю.

У меня на данный момент задумка довольно элементарная: в зависимости от ситуации назначить файл/поток для ввода-вывода.

Например: Если процесс демонизируется, то для вывода назначить log файл. Если программа работает в обычном режиме, то писать в cout/stdout, cerr/stderr.

В обычном (ламповом) С все просто:

FILE * fout = fopen("logfile", "a+");

или:

FILE * fout = stdout

А как подобное сделать с потоками?

Да и нужны ли эти потоки?

В чем их преимущество?



Последнее исправление: HighMan (всего исправлений: 2)

Используй сишные функции, никто не запрещает. А потоки нужны для перегрузки операторов, ты можешь создать любой класс и перегрузить оператор вывода или ввода, пример такого класса это std::string.

Werenter ★★☆
()
Ответ на: комментарий от Werenter

Используй сишные функции, никто не запрещает. А потоки нужны для перегрузки операторов, ты можешь создать любой класс и перегрузить оператор вывода или ввода, пример такого класса это std::string.

Это понятно!

Но в C++ можно перегрузить, например, fprintf, которым пользоваться несравнимо удобнее!

HighMan
() автор топика

Сшные принтф нетипобезопасен. Если перепутать спецификатор (буковки после % в строке форматирования) то можно поймать что то не то.

Но можно конечно перегрузить принтф через variadic template безопасным образом. Если этого ещё не сделали в стандартной библиотеке, хз.

Так то я сам чаще свой перегруженный printf и юзаю, меньше букофф.

AntonI ★★★★★
()

В обычном (ламповом) С все просто:

#include <iostream>

int main() {
  auto &fout = std::cout;
  fout << "Hello";
  std::cout << " World!";
  fout << std::endl;
}

Где-то писали, что в С++ можно использовать stdio (без «.h»), но я таковой у себя в Fedora 37 не обнаруживаю.

cstdio, так же с другими заголовочниками, stdint.h -> cstdint

Но я не могу понять в чем преимущество потокового ввода-вывода?

Можно реализовать свой поток для какого то своего ресурса, RAII.

Да и нужны ли эти потоки?

Нужны, но можно не использовать.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

Можно реализовать свой поток для какого то своего ресурса

Это и в stdio можно. И вообще, в stdio это тоже потоки. Главное отличие - в синтаксисе с встроенной проверкой типов ценой ухудшения читабельности кода (речь не про helloworld а когда надо что-то сложное вывести, например лаконичное %10.50s в iostream если и можно, то уж только будет занимать раз в 10 больше байт исходника и зрительно его замусоривать). Насколько это кому нужно сам каждый решает.

firkax ★★★★★
()

У меня на данный момент задумка довольно элементарная: в зависимости от ситуации назначить файл/поток для ввода-вывода.

В чём проблема? std::ostream& может быть ссылкой как на stdout, так на файловый поток и даже на stringstream.

А так основное преимущество потоков в типизации и безопасности. Во-первых, тебе не нужно писать format specifier как в printf и следить за тем чтобы он совпадал с типом значения (тут очень много подводных камней, с time_t например). Во-вторых, можно один раз определить operator<< для кастомного типа и печатать его откуда угодно ни о чём не думая.

slovazap ★★★★★
()

В чем их преимущество?

преимущество - в типизации. Для любого типа можно запилить собственный оператор вывода и использовать в потоках. В то время как функции из stdio.h реализованы на сишных varargs, со всеми вытекающими.

в то же время, iostreams под капотом - это куча говнокода, который заспамливает бинарь и еще и может работать медленнее чем stdio.h.

В общем, если по-честному то оба варианта не очень, надо смотреть в сторону сторонних библиотек или реализовать что-нибудь свое.

Lrrr ★★★★★
()
Ответ на: комментарий от slovazap

тут очень много подводных камней, с time_t например

Это не подводные камни, а int-нубство. Суть int-нубства вот в чём: для всех целых типов бездумно суём int, для всех выводов их пишем %d. Разумеется, спецификатор формата должен строго соответствовать присланному типу и, поскольку для time_t его нет, его надо конвертировать либо в ([unsigned] long) либо, если хотим поддержку 2038 года на 32-бит платформе - в ([unsigned] long long) и писать соответственно %ld/%lu или %lld/%llu. И никогда - %d.

На таки да, автоматическая проверка типа перегруженными операторами удобна.

firkax ★★★★★
()
Ответ на: комментарий от MOPKOBKA

man fopencookie

Открываешь поток этой функцией, даёшь ей колбеки для нужных действий.

Единственный минус - в винде её наверно нет и в старых ОС. Но аналогичный функционал, хоть в другом виде, думаю и там есть/был. Врядли хоть где-то stdio захардкожен на файловые сисколлы, это как минимум неудобно для самих же авторов системной библиотеки (sprintf придётся отдельно делать, а не враппером над виртуальным потоком в памяти).

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

МОРКОВКА, в приведенном вами примере fout лишь «псевдоним» для cout.

Мне же нужен поток, который может быть потоком в файл или терминал. В зависимости от ситуации.

HighMan
() автор топика
Ответ на: комментарий от firkax

Не псевдоним а ссылка. Ссылку можно переписать на другой объект во время работы проги, как тебе и надо.

Не могли бы кинуть кусочек кода?

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

HighMan
() автор топика
Ответ на: комментарий от HighMan
#include <iostream>
#include <fstream>

int main(int argc, char **argv) {
  std::ofstream file("file.txt");
  auto &fout = argc > 1 ? file : std::cout;
  fout << "Hello";
  std::cout << " World!";
  fout << std::endl;
}  
MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

А как объявить поток, направленный хз куда глобально? И что бы его можно было менять в процессе выполнения программы?

HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 1)
Ответ на: комментарий от HighMan

А как объявить поток, направленный хз куда глобально?

А зачем так делать? Ммм?

И что бы его можно было менять в процессе выполнения программы?

fout = x;

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA
#include <iostream>
#include <fstream>

using namespace std;

auto &log = cout;

int main(int argc, char ** argv){
    fstream f("test.txt", ios::out | ios::app);
    if(f.is_open())
        auto &log = f;
    return 0;
}

предупреждение: built-in function «log» declared as non-function [-Wbuiltin-declaration-mismatch] 6 | auto &log = cout;

HighMan
() автор топика
Ответ на: комментарий от HighMan
if (f.is_open()) log.rdbuf(f.rdbuf());

как-то так. У объектов потоков удалены конструкторы копирования, но им можно расшаривать буферы таким вот образом.

Lrrr ★★★★★
()
Последнее исправление: Lrrr (всего исправлений: 1)
Ответ на: комментарий от firkax

Нет, это подводные камни. То что ты высокомерно называешь это нубством делает нубом тебя, но не делает C языком программирования. Невозможность вывести стандартным способом простейший скалярный тип - это дикость, как и предложение кастовать типы только для их вывода. Не говоря о том что это не всегда вообще возможно, как, например, в случае типа неспецифицированной знаковости максимального размера.

slovazap ★★★★★
()
Ответ на: комментарий от slovazap

Нет, это подводные камни.

Нет, это именно нубство. И дело не в time_t и не в printf, а в том, что кто-то «дефолтный целый тип» себе воображает. Хотя, к этому есть исторические основания - в старые времена пропущеное указание типа, дефолтящееся в int, не считалось чем-то ужасным, и даже новые компиляторы для совместимости поддерживают это. Но, тем не менее, делать так сейчас - крайне плохая практика вне зависимости от конкретных связанных проблем, и тот, кто так делает, однозначно нуб.

Невозможность вывести стандартным способом простейший скалярный тип

time_t это не простейший тип, а алиас к неспецифицированному типу. И да, в си нет перегрузки функций, поэтому компилятор сам угадывать какую вызвать на нужный тип - не будет. В целом ничего плохого, просто такой язык.

типа неспецифицированной знаковости

Где ты такое нашёл? Это как раз дикость.

firkax ★★★★★
()

Но я не могу понять в чем преимущество потокового ввода-вывода?

Он гибче. Например, помимо выхода в файл или терминал умеет сохранять буковки в строку.

На мой взгляд, stdio.h удобнее в использовании как для вывода в терминал, так и для файловых операций.

Удобнее. Поэтому в C++ 20 и 23 запилили std::format и надстройки над ним.

Esper
()

потоки (iostream) в С++ это дань традиции. В этом муравейнике так принято со времён Страуструпа. Можешь использовать иное, но ровно до момента плотного взаимодействия со товарищи. Поэтому в массе потоки используют почти только для логов и тестов.

MKuznetsov ★★★★★
()
Ответ на: комментарий от HighMan

Через глобальные переменные с потоками работать не принято (да, да, cout). Обычно у тебя будет функция, принимающая ссылку на ostream или шаблонный тип, ты можешь вызвать ее с аргументом cout или ofstream или stringstream — по обстоятельствам.

filosofia
()

printf небезопасен (можно ошибиться в спецификаторе и случится UB), а также не умеет (портабельно) выводить пользовательские типы (а iostream позволяет перегрузить операторы >> и << для своих типов).

Плюсрвой алиас для stdio.h - cstdio (то же самое работает со многими другими заголовочными файлами - climits, cstring и т. д.).

Если ты делаешь логгирование, возьми какую-набудь плюсовую библиотеку логгирования - spdlog, easyloggingpp - тысячи их. Там будет из коробки и поддержка динамического конфигурирования вывода на экран/в файл (в том числе одновременно), и форматирование, и таймстемпы, и потокобезопасность, и ротация логов. Не изобретай велосипед без надобности.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 4)
Ответ на: комментарий от MOPKOBKA

А зачем так делать? Ммм?

Мне, в данном случае, удобнее объявить «переменную» глобально, возможно, в процессе, буду передавать параметром. Но! Главная задача: единая переменная, например fout, которую можно переопределять, в процессе работы программы, для вывода в консоль или файл.

В C все просто и понятно, а вот с потоками - затык(

HighMan
() автор топика
Ответ на: комментарий от HighMan
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main(void){
    ostream * os = &cout;
    *os << "Terminal output" << endl;
    fstream f("log.log", ios::out | ios::app);
    if(!f.is_open()){
        *os << "Can't be open file!" << endl;
        return 1;
    }
    os = &f;
    *os << "File output" << endl;
    return 0;
}

Вот так работает. Только одна неприятность: нужно писать *os. Это можно обойти?

HighMan
() автор топика
Ответ на: комментарий от bugfixer

Хм, не знал, ну значит надо использовать указатель. Я думал ссылки это просто синтаксическая конструкция для неявного использования указателей. Не особо С++ использую.

firkax ★★★★★
()
Ответ на: комментарий от firkax

значит надо использовать указатель.

Именно.

Я думал ссылки это просто синтаксическая конструкция для неявного использования указателей.

Так и есть, с нюансами типа binding unnamed temporaries etc.

На самом деле поправлюсь - теоретически я думаю можно извратиться с placement new и таки ссылочку изменить (если она завёрнута в структурку - так точно), но я не думаю что это то чего в этом контексте хочется.

bugfixer ★★★★★
()
4 июля 2023 г.

основное преимущество iostream (и подобного другого из стандартного), что внутри вызывается std::locale, что обеспечивает работу с выбранной нужной локалью.
Для того, что бы на каждые >> и << не вызывалась std::locale сделали std::format в с++20, где гарантировано должна быть вызвана std::locale только однажды. А еще в c++23 сделали std::print — как std::format, только сразу выводит в стрим или на стандартный вывод.

safocl ★★
()