Trouble with logfile output - difference between string and c_str - C++ - c++

I am trying to finalize my logging class. I have written it from scratch and do not wish to use an alternative library of any kind. My problem lies within the fact that my logger has trouble outputting std::strings and only works when I denote it with the string.c_str() function.
Here is my logfile output function:
void Log::writeSuccess(char * text,...)
{
// Grab the variables and insert them
va_list ap;
va_start(ap, text);
char buff[BUFFER_SIZE];
vsnprintf(buff, sizeof(buff), text, ap);
// Output to the log
logfile << "<-!-> " << buff << endl;
}
Here is a sample call to my log class object (ignore the uselessness of the call):
string test("This is a test string!");
errorLog.writeSuccess("Output: %s", test);
I end up with random characters and garbled output.
However, when I append the string, test with .c_str(), it outputs the text correctly.
The whole reason I am trying to avoid cstrings is because I understand they are not cross platform and am developing my client to support all the major operating systems.
To summarize:
What is wrong with my log output function? Do you see any way it could be improved?
Should I generally avoid c_strings?

You're getting random garble when passing an std::string to vsnprintf because the format specifier "%s" is that of a C-string - a char*.
std::string is not of type char*, but std::string.c_str() is of type char*. vsnprintf will basically read chars pointed to by the address that it presumes is that of a start of a C-string, up until the NUL character '\0'.
An std::string pushed onto the stack and passed as an argument to vsnprintf is not a pointer to a char, however vsnprintf will just treat these bytes as an address and start reading chars/bytes from this address, causing undefined behaviour.
The printf family of functions are not typesafe, since they rely on a format string and variable argument list, which is why your code will compile but you'll get unexpected results.
Bottom line is the printf family of functions expect a char* when you use the format specifier "%s".
I also think you're confusing C style strings (char[]) with the Microsoft-specific CString class. C style strings won't cause you problems on different platforms at all; the literal "This is a test string!" is a C style string (const char[]).

When calling function with variable parameters you must use simple types. For string you must use c_str(). There's no workaround. MFC's CString is designed so that you can get away with using it directly, but that was Microsoft's decision, and part of their design.
EDIT: As I said when calling function with variable parameters you must use string::c_str(). However, instead of C-like functions with variable parameters you can use something like boost::format() and it's parameter feeding operator %. This also gives you more control over ordering of parameters, which is very handy for i18n.

Related

why is this filename parameter a char pointer instead of string?

There's a function provided by my uni that is supposed to read in a file. In the parameter, there's the fileName parameter. What I don't understand is why they're using a character pointer instead of a simple string?
Also, how would I call this function with a string filename then?
I'm running C++14 on Visual studio 2017 community edition.
double* read_text(char *fileName, int sizeR, int sizeC)
{
double* data = new double[sizeR*sizeC];
int i = 0;
ifstream myfile(fileName);
if (myfile.is_open())
{
while (myfile.good())
{
if (i > sizeR*sizeC - 1) break;
myfile >> *(data + i);
i++;
}
myfile.close();
}
else cout << "Unable to open file";
//cout << i;
return data;
}
Edit: I get it guys, it's stupid. I'll post a separate question then. Thanks for the fast response!
why is this filename parameter a char pointer instead of string?
Because the designer of the API chose to do so. Most people at Stack Overflow are not the author of that API, so we cannot answer this question accurately.
However, there is another similar question that we can answer: What are reasons to use character pointer as a string argument? Let me answer that question instead:
Not using std::string as an argument allows the user of the API to not create a std::string object. This may be useful when:
The API needs to be used in "freestanding" implementations that don't provide the standard library and thus have no std::string. This probably doesn't apply to your particular example since the implementation of the function uses the standard library, but I include this argument for completeness.
The API needs to be used on systems that provide no dynamic memory allocation, which std::string requires.
The dynamic memory allocation that creation of the string may require may be too slow in the context where the API is used (this won't apply to an API that is going to read from disk, but I include the argument here for completeness).
(const) char* makes it possible to use the API from C. This may be relevant because:
The API may have originally been written for C, and has been inherited to a code base that now uses C++, but the API has not been changed in order to maintain backwards compatibility.
Providing a C compatible API allows using the API from other languages that are able to interface with C. Many languages have support for C interfaces while very few languages have support for C++ interfaces.
Also, how would I call this function with a string filename then?
You can get a pointer to a null terminated string using the c_str member function. Unfortunately the API is badly designed and the argument is non-const, while the pointer returned by c_str is const. You can const_cast the constness of the argument away in order to call this function. This is "OK" because the function doesn't actually modify the pointed string.
If you can require C++17 standard and the source string is non-const, then the data member function will be simpler as it doesn't require const_cast. Prior to C++17 there was no non-const overload, so it would require the same const casting, and prior to C++11 the pointed string was not guaranteed to be null terminated.
To make it clear: Using a non-const string argument for this function is bad design - whether that argument is a character pointer to null terminated string, or a reference to std::string.
P.S.There are other, more serious problems:
The caller of the function cannot possibly know how many numbers were read from the file. There is no way of avoiding UB in case the file has less than sizeR*sizeC values.
Returning a bare pointer that owns dynamic memory resource is a very bad design.
The loop that reads from the file checks whether the read was successful after the value has already been added to the array and the value is never overwritten, so the last element written into the array is always has an unspecified value.

How does using a variable for file paths work?

ifstream inFile;
inFile.open("C:/FilePathThatWorks");
The above seems to work in C++. But if I try and take a String, or CString, or anything else, and plug it in for inFile.open(ExampleString), it fails at compile time (error at the bottom of this question). The question is not about my code, but how to make C++ accept a variable for inFile.
If it only ever accepts char* variables, then is there perhaps a method that takes input from the user in the form of a char* with the null terminator and everything.
error: no matching function for call to 'std::basic_ifstream<char>::open(std::__cxx11::string&)'
The method open in fstream does not accept std::string. You need to pass a char*.
So just call c_str() on string.
ifstream requires a char* param for the filename. see http://www.cplusplus.com/reference/fstream/ifstream/ifstream/
most string classes have some way to convert to this kind of string (e.g. c_str())
If you want to use CString:
CString path = "c:\\FilePathThatWorks";
ifstream inFile;
inFile.open(static_cast<const char*>(path));
The cast is not needed here.
CString can represent two different strings depending on the project settings. If it is a string of char the code should compile, if the character type is wchar_t compiling should fail.

Are the methods in the <cstring> applicable for string class too?

I've tried out using memcpy() method to strings but was getting a "no matching function call" although it works perfectly when I use an array of char[].
Can someone explain why?
www.cplusplus.com/reference/cstring/memcpy/
std::string is an object, not a contiguous array of bytes (which is what memcpy expects). std::string is not char*; std::string contains char* (somewhere really deep).
Although you can pull out the std::string inner byte array by using &str[0] (see note), I strongly encourage you not to. Almost anything you need to do already is implemented as a std::string method. Including appending, subtracting, transforming and anything that makes sense with a text object.
So yes, you can do something as stupid as:
std::string str (100,0);
memcpy(&str[0],"hello world", 11);
but you shouldn't.
Even if you do need memcpy behaviuor, try to use std::copy instead.
Note: this is often done with C functions that expects some buffer, while the developer wants to maintain a RAII style in his code. So he or she produces std::string object but passes it as C string. But if you do clean C++ code you don't need to.
Because there's no matching function call. You're trying to use C library functions with C++ types.

Pass CString to fprintf

I have ran the code analyzer in visual studio on a large code base and i got about a billion of this error:
warning C6284: Object passed as parameter '3' when string is required in call to 'fprintf'
According to http://msdn.microsoft.com/en-us/library/ta308ywy.aspx "This defect might produce incorrect output or crashes." My colleague however states that we can just ignore all these errors without any problems. So one of my questions is do we need to do anything about this or can we just leave it as is?
If these errors need to be solved what is the nicest approach to solve it?
Would it work to do like this:
static_cast<const char*>(someCString)
Is there a better or more correct approach for this?
The following lines generate this warning:
CString str;
fprintf(pFile, "text %s", str);
I'm assuming that you're passing a Microsoft "CString" object to a printf()-family function where the corresponding format specifier is %s. If I'm right, then your answer is here: How can CString be passed to format string %s? (in short, your code is OK).
It seems that originally an implementation detail allowed CString to be passed directly to printf(), and later it was made part of the contract. So you're good to go as far as your program being correct, but if you want to avoid the static analysis warning, you may indeed need to use the static_cast to a char pointer. I'm not sure it's worth it here...maybe there's some other way to make these tools place nice together, since they're all from Microsoft.
Following the MSDN suggestions in C6284, you may cast the warnings away. Using C++ casts will be the most maintainable option to do this. Your example above would change to
fprintf(pFile, "text %s", static_cast<const TCHAR*>(str));
or, just another spelling of the same, to
fprintf(pFile, "text %s", static_cast<LPCTSTR>(str));
The most convincing option (100% cast-free, see Edits section) is
fprintf(pFile, "text %s", str.GetString());
Of course, following any of these change patterns will be a first porting step, and if nothing indicates a need for it, this may be harmful (not only for your team atmosphere).
Edits: (according to the comment of xMRi)
1) I added const because the argument is read-only for fprintf
2) notes to the cast-free solution CSimpleStringT::GetString: the CSimpleStringT class template is used for the definition of CStringT which again is used to typedef the class CString used in the original question
3) reworked answer to remove noise.
4) reduced the intro about the casting option
Technically speaking it is ok because the c-string is stored in such a way in CString that you can use it as stated but it is not good rely on how CString is implemented to do a shortcut. printf is a C-runtime function and knows nothing about C++ objects but here one is relying on an that the string is stored first in the CString - an implementation detail.
If I recall correctly originally CString could not be used that way and one had to cast the CString to a c-string to print it out but in later versions MS changed the implementation to allow for it to be treated as a c-string.
Another breaking issue is UNICODE, it will definitely not work if you one day decide to compile the program with UNICODE character set since even if you changed all string formatters to %ld, embedded 0s will sometimes prevent the string from being printed.
The actual problem is rather why are you using printf instead of C++ to print/write files?

getline in istream and getline in basic_string

string text;
getline(text.c_str(),256);
1) I am getting an error "error: no matching function for call to 'getline(const char*, int)"
What's wrong in the above since text.c_str() also returns a pointer to array of characters.
If I write like this
char text[256]
cin.getline(text, 256 ,'\n');
it works fine. What's the difference between cin.getline and getline?
2) How come
text string;
getline(cin,text,'\n')
accepts the whole line as the input. Where is the pointer to array of characters in this one?
text.c_str() returns a const char *. You may not use it to modify the contents of the string, in any way. It only exists so that you can pass the string data to old C API functions without having to make a copy. You are not allowed to make changes because there is no way that the string object that holds the data could possibly find out about them, and therefore this would allow you to break the string's invariants.
Furthermore, std::getline accepts completely different parameters. (You would know this if you took two seconds to type 'std::getline' into Google.) The error means exactly what it says: "no matching function for call" means "you can't call the function with these kinds of parameters", because every overload of the function accepts something different (and incompatible).
std::getline accepts these parameters:
A stream. You have to pass this because otherwise it doesn't know where to read from.
A string object to read into. NOT a char buffer.
Optionally, a line delimiter char (same as the stream getline member function).
There is not really any such function as "cin.getline". What you are calling is the member function "getline" of the object "cin" - a global variable that gets defined for you when you #include <iostream>. We normally refer to this according to what class the function is defined in - thus, std::istream::getline.
std::istream::getline accepts these parameters:
A char buffer.
Optionally, a line delimiter char.
It does not need a stream parameter because it is a member function of the stream: it uses whatever stream we called it with.
I don't really get what the questioner is trying to do.
C++ allows function overloading, so the compiler is looking for a free function called getline that matches the parameters you have passed, and no such function exists, nor should it exist (what would getline(const char*, int) do anyway?)
The question has been asked many times why getline(istream&, string&) is a "free" function and not part of the iostream interface. Answers suggested have been that iostream outdates STL or that iostream has no dependent on the basic_string class (anywhere, which is also why opening files is done with raw pointers), and Herb Sutter would commend making getline a free-function because he feels class member functions should be minimal (and std::string has far too many, eg the find functions which could be free ones that use the class).
One thing about that function though is how useful it is as you do not need to pre-allocate a buffer and "guess" how big to make it. (Having said that if you read from a big file into a std::string you could bad_alloc if there are no newlines to be found!).
string::c_str() returns a const char *, not a char *. getline works with a string, so what you want is
string text;
getline(cin, text, '\n');
This version of getline allows the string to grow as much as needed. If you need to limit it to a certain number of characters, you would need to use a vector<char> as in the previous answer.
text.c_str() is an array of const characters. The const means neither you nor any function (including any sort of getline) may write into it.
There's no portable way to use a string as a writeable array of characters. But when necessary, you can use a vector<char> instead:
std::vector<char> buffer(256);
std::size_t len = cin.getline(&buffer[0], buffer.size());
std::string text(&buffer[0], len);
But just using the string overload of getline is probably best here.