Why Does (I|O)Fstream Take a Const Char* Parameter for a File Name

Why does (i|o)fstream take a const char* parameter for a file name?

Class std::string implements the concept of "run-time-sized resizable string". This is when this class should be used - when you need a string whose size is only known at run-time and which is run-time resizable as well. In situations when you don't need these features using std::string is an overkill. Apparently, the authors of the library didn't think that they needed a run-time resizable string to represent a file name, so they opted for a minimalistic solution: they used a C-string where a C-string was sufficient. This is actually a very good principle for designing library interfaces: never require something that you don't really need.

It is true that these days we often see people who encourage C++ programmers to use std::string whenever they need a string, any string. They often claim that classic C strings should be reserved to C code. In general case this is a bogus philosophy. Gratuitous use of comparatively heavy objects like std::string is more appropriate in languages like Java, but is normally unacceptable in C++.

Yes, it is possible to get away with using std::string all the time in some C++ applications ("it is possible to write a Java program in C++"), but in such a generic low-level library as C++ standard library forcing the user to use std::string without a good reason (i.e. imposing unnecessary requirements) would not look good.

Why does ostream::write() require ‘const char_type*’ instead of ‘const void*’ in C++?

The portrayal of this as a C vs C++ thing is misleading. C++ provides std::fwrite(const void*, ...) just like C. Where C++ chooses to be more defensive is specifically the std::iostream versions.

"Almost in all cases the binary data to be written to files is not char array"

That's debatable. In C++ isn't not unusual to add a level of indirection in I/O, so objects are streamed or serialised to a convenient - and possibly portable (e.g. endian-standardised, without or with standardised structure padding) - representation, then deserialised/parsed when re-read. The logic is typically localised with the individual objects involved, such that a top-level object doesn't need to know details of the memory layout of its members. Serialisation and streaming tends to be thought of / buffered etc. at the byte level - fitting in better with character buffers, and read() and write() return a number of characters that could currently be transmitted - again at the character and not object level - so it's not very productive to pretend otherwise or you'll have a mess resuming partially successful I/O operations.

Raw binary writes / reads done naively are a bit dangerous as they don't handle these issues so it's probably a good thing that the use of these functions is made slightly harder, with reinterpret_cast<> being a bit of a code smell / warning.

That said, one unfortunate aspect of the C++ use of char* is that it may encourage some programmers to first read to a character array, then use inappropriate casts to "reinterpret" the data on the fly - like an int* aimed at the character buffer in a way that may not be appropriately aligned.

If you want to print a stream of characters, you could use operator<<(). Isn't write() method designed for writing raw data?

To print a stream of characters with operator<<() is problematic, as the only relevant overload takes a const char* and expects a '\0'/NUL-terminated buffer. That makes it useless if you want to print one or more NULs in the output. Further, when starting with a longer character buffer operator<< would often be clumsy, verbose and error prone, needing a NUL swapped in and back around the streaming, and would sometimes be a significant performance and/or memory use issue e.g. when writing some - but not the end - of a long string literal into which you can't swap a NUL, or when the character buffer may be being read from other threads that shouldn't see the NUL.

The provided std::ostream::write(p, n) function avoids these problems, letting you specify exactly how much you want printed.

ifstream constructor taken a char* but not a std::string

No. The constructor from std::string was added in C++11.

Why does fstream::open() expect a C style string?

You are misunderstanding the syntax for the function signatures:

Why does fstream::open() expect a const String/ const char* ? The
filename could be obtained from anywhere (from a user, like in the
example above), and making str into a const string doesn't help in
that case.

The signatures are below for reference:

void open (const char* filename, ios_base::openmode mode = ios_base::in | ios_base::out);
void open (const string& filename, ios_base::openmode mode = ios_base::in | ios_base::out);

The first overload takes a const char* (e.g. a pointer to a constant character array).
The second overload takes a const std::string& (e.g. a reference to a constant std::string).

It is not a const std::string, but a reference to a string. It is stating that the function will not modify the string, and you are passing a reference to the original (instead of copying it). You can pass a non-const string to a function that requires a const string without an issue (there is no conversion/cast required).

The answer to your original question (Why does it take a const char*?) is not all that complicated: The stream libraries and the string libraries were developed in parallel by different groups of people. In the early standards, they didn't merge the development. That has been addressed in C++11.

Ofstream, use variable to the name

Either side of operator+ must be a std::string1 for operator+ to concatenate strings:

string name;
cin >> name;

ofstream PlayerPawn("D:\\UDK\\UDK_XXX\\Development\\Src\\" + name + "\\Classes\\_PlayerPawn.us");

And use std::string for this stuff; with std::string there's no danger of buffer overflows that you get with char*.


1 Actually it just needs to be a class type that supports operator+, not specifically std::string, but then you have no idea what it will do.

Why does std::ifstream constructor not take a std::string?

It was a simple omission. Nobody thought about it in time. This has been corrected in C++11, where std::string is also accepted. From 27.9.1.7/3:

explicit basic_ifstream(const string& s, ios_base::openmode mode = ios_base::in);

Effects: the same as basic_ifstream(s.c_str(), mode).

C++ ifstream error using string as opening file path.

Change

ifstream file(filename);

to

ifstream file(filename.c_str());

Because the constructor for an ifstream takes a const char*, not a string pre-C++11.

code do not read text from file c++

One of the issues of your program is that your function take a pointer to Mutable, R/W character array:

void HowManyWords(char Filename[]);

In the main function, you are passing it a const char string. Text literals are constant.

If you are not changing the contents of Filename, pass it as "read-only":

void HowManyWords(char const * Filename)

Reading the type from right to left, this is a pointer to constant ("read only") char. The function is stating that it will not change the contents of Filename. Thus you can pass it a string literal.

For more information, search the internet for "c++ const correctness pointers".

Edit 1: simple example
Here is a simple working example showing the correct parameter syntax for your HowManyWords function:

#include <stdio.h>

void HowManyWords(const char Filename[])
{
puts("Filename: ");
puts(Filename);
puts("\n");
}

int main()
{
HowManyWords("test.txt");
puts("\n");
return 0;
}

Here is the compilation and output, using g++ on Cygwin, on Windows 7:

$ g++ -o main.exe main.cpp

$ ./main.exe
Filename:
test.txt

$

As I stated above, comment out your code in HowManyWords and get the parameter passing working correctly. Next, add a little bit of code; compile, test and repeat.

Can I create fstream before main?

You cannot initialize your fout before you know what argv is. So, in some sense, impossible. However, you can initialize it with nothing, and open it later when you get your argv:

ofstream fout;

int main(int argc, const char * argv[])
{
fout.open(argv[1]);
fout<<"new words"<<endl;
}

This way, the global fout is accessible to all functions defined after it. However, there is a short period, before opening it in main, when fout is invalid. This is the best you can do, from a logical point of view.



Related Topics



Leave a reply



Submit