1
votes

I am making a toy program to create classes from a string (so that e.g. I feed it "test1 test2" and it makes test1.cpp, test1.h, test2.cpp, test2.h)

The action happens in this function:

bool makeClassesFromString(std::string classNames){

    bool returnValue = true;

    if (classNames == ""){

        returnValue = false;

    }
    else{

        std::istringstream issClassNames (classNames);
        std::string sClassName;

        while(issClassNames.good()){

            issClassNames >> sClassName;
            std::string sourceName = sClassName;
            sourceName += ".cpp";

            std::string headerName = sClassName;
            headerName += ".h";

            std::ofstream source(sourceName.c_str()), std::ios::app);
            std::ofstream header(headerName.c_str()), std::ios::app);

            //source << "#include \"" << headerName << "\"";


            source.close();
            header.close();

        }

    }

    return returnValue;

}

The files are opened in append mode to ensure that any classes that already exist aren't accidentally overwritten.

I recently returned to this program to include some extras - in particular, the old version just created two empty files and I wanted to modify it to create the files with the necessary defines and includes so I wouldn't have to manually go in and add them each time. This revealed unexpected behavior - the last class to be made gets any text added twice.

In the code sample above, I've commented out the line where I started working on this but when I don't comment it out, I get behavior like this:

input:
classmaker.exe test1 test2

output:
test1.h
test2.h
test1.cpp //(Which contains: #include "test1.h")
test2.cpp //(Which contains: #include "test2.h"#include "test2.h"

My guess is that my while(issClassNames.good()) returns an extra time with the last classname (and so appends the string to the source file again) but I am not certain what behavior drives this.

Proposed solutions so far:

  1. Test whether file already exists attempting to open in ifstream mode before opening in ofstream mode. (Problem: Doesn't solve issue of running while once mode than necessary)
  2. Remove append mode. (Problem: Risks overwriting already existing class, still doesn't solve the while-loop issue)
  3. Use conditional test in while-loop, don't open files on last run by e.g. comparing current class name with last class name and aborting if equal. Problem: Not sure how to check whether this is last run except cludgy "is this the same class name as last time?" test which might also risk aborting too early if the input string is "test1 test1 test2"

EDIT

I have realized: It's not really that the istringstream.good() returns true once more than expected, it is rather that it evaluates like so:

input: "test1 test2"

evaluation:
good = true; extract next to string //(succeeds, overwrites empty string with "test1")
good = true; extract next to string //(succeeds, overwrites "test1" with "test2")
good = true; extract next to string //(fails, Doesn't overwrite "test2")
good = false; break loop.

EDIT 2 + 3

Well that was easily solved. Thank you, all of you. Unfortunately I can only mark one answer as accepted so I picked the first posted answer but I appreciate the help.

The final program in its entirety:

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>

void makeClassesFromStdInput();
void makeClassesFromParameter(int argc, const char** argv);
bool makeClassesFromString(std::string classNames);

int main(int argc, const char** argv){

    switch (argc) {

        case 0:  //This shouldn’t happen

            break;

        case 1:

            //This will keep making classes from the CLI until
            //the user breaks the process.
            makeClassesFromStdInput();
            break;

        default:

            //This will make classes from the parameters passed
            //to the program except the first parameter which is
            //assumed to be the program name.

            makeClassesFromParameter(argc, argv);
            break;

    }

    return 0;

}

void makeClassesFromStdInput(){

    std::cout << "Input class name and press enter.\nWhitespace delimits class names.\nA blank line ends the process.\n>";
    bool running = true;
    std::string classNames;

    while(running){

        std::getline(std::cin, classNames);
        running = makeClassesFromString(classNames);

    }

}

void makeClassesFromParameter(int argc, const char** argv){

    std::string classNames = "";

    for(int i = 1; i<argc; i++){

        classNames += argv[i];
        classNames += " ";

    }

    makeClassesFromString(classNames);

}

bool makeClassesFromString(std::string classNames){

    bool returnValue = true;

    if (classNames == ""){

        returnValue = false;

    }
    else{

        std::istringstream issClassNames (classNames);
        std::string sClassName;

        while(issClassNames >> sClassName){

            std::string sourceName = sClassName;
            sourceName += ".cpp";

            std::string headerName = sClassName;
            headerName += ".h";

            std::ofstream source(sourceName.c_str(), std::ios::app);
            std::ofstream header(headerName.c_str(), std::ios::app);

            source << "#include \"" << headerName << "\"";

            //Header is slightly more involved because we want all capital letters
            for (std::string::size_type i=0; i<headerName.length(); ++i){

                if(headerName[i] == '.'){

                    headerName[i] = '_';
                }
                else{

                    headerName[i] = toupper(headerName[i]);
                }
            }

            header  << "#ifndef __" << headerName << "\n"
                    << "#define __" << headerName << "\n"
                    << "\n"
                    << "\n"
                    << "#endif";

            source.close();
            header.close();

        }

    }

    return returnValue;

}
3

3 Answers

4
votes

The stream can't know what you are about to read. You always need to check after you tried to read if your read attempt was successful:

while(issClassNames >> sClassName) {
    // do whatever processing of sClassName you want to do
}
2
votes

Try changing:

while(issClassNames.good()){

to:

while(issClassNames >> sClassName){

And take that line out of the top of the loop.

Stream operator >> returns a stream, which can be cast to a bool with the result being the result of good(). Since the returned stream is in the state after the call of >>, it will return false if the eof bit was set by the operation.

2
votes

You should replace

while(issClassNames.good()){
  issClassNames >> sClassName;

with

while(issClassNames >> sClassName) {

WHY
Assume your input is test1 test2. You first check stream is good. As there are no errors, it is good so you read test1 in sClassName. Now still the stream is good, so you go ahead and read test2 in sClassName (without an error in this step). Stream is still good so you go ahead and try to read again, but operator >> fails and sClassName remains unchanged and thus you see the old value test2.

operator >> returns handle to stream itself (which is convertible to bool) and can we used in condition. So you would enter the loop only if the read was succesful.