user2780856
user2780856

Reputation: 49

C++ Separate header and source file

I'm new to C++ and so far I've had all code in one and the same file. Now as I'm progressing, I need to separate my code in to source and header files which I'm not very familiar with.

I can make it work with simple tasks, but in this program that I'm now trying to divide in to separate files gives me an error, while when I have it all in one file I can compile it.

I get stuck on the error message

main.cpp:10:1: error: unknown type name 'textEditor'
textEditor siEditor;

If someone could explain why I'm running in to this error and how to prevent it would be appreciated. I read that it could be related to duplicated declarations, but I don't understand from where.

This is how my main.cpp looks:

#include <iostream>
#include <fstream>

using namespace std;

#include "textData.h"
#include "textEditor.h"

textData siData;
textEditor siEditor;

int main() 
{
    cout << "\nWelcome to siEdit!" << endl;
    while (true) 
    {
        cout << "\nWhat would you like to do? \nNew file = n, Append = a, View = v, Quit = q: ";
        string toDo;
        cin >> toDo;

        if (toDo == "n")
        {
            siEditor.openText();
            cout << "Now editing the file: " << siData.fileName.c_str() << endl;
            cout << "Type '=!' to stop editing and save. \n " << endl;
            siEditor.writeText();
        }

        else if (toDo == "a")
        {
            siEditor.appendTextOpen();
            cout << "Now appending text: " << siData.appendTextfileName.c_str() << endl;
            cout << "Type '=!' to stop editing and save changes. \n " << endl;
            siEditor.appendText();
        }

        else if (toDo == "v")
        {
            siEditor.readText();
            cout << "\n";
        }

        else if (toDo == "q")
        {
            return 0;
        }

        else 
        {
            cout << "Invalid input." << endl;
        }
    }
}

siEdit.cpp:

#include <iostream>
#include <fstream>

using namespace std;

#include "textData.h"
#include "textEditor.h"

textData siData;

class textEditor
{
    public: 
    void openText()
    {   
        //when associated file is open. 
        while (siData.siFile.is_open())
        {
            siData.siFile.close();
        }
        cout << "\nWhat do you want to call your file? ";
        cin >> siData.fileName;

        //Creates / Opens fileEditor
        const char* path = siData.fileName.c_str();
        siData.siFile.open(path);
    }

    void writeText()
    {
        bool editing = true;
        bool hasEditing = false;

        while (editing == true)
        {
            //Get user input
            string input = " ";
            getline(cin, input);
            string yesNo;

            if (input == "=!") 
                {   
                    cout << "Would you like to save the file? Y/N" << endl;
                    cin >> yesNo;

                    if (yesNo == "Y")
                    {
                        cout << "Filed saved: " << siData.fileName.c_str();
                        editing = false;
                    }   

                    else if (yesNo == "N")
                    {
                        cout << "No changes have been saved. Exiting." << endl;
                        hasEditing = false;
                        editing = false;
                        siData.siFile.clear();
                    }

                    else 
                    {
                        cout << "Invalid input. Type '=! to exit." << endl;

                    }
                }       

            else
            {
                siData.siFile << input;
                siData.siFile << endl;
                hasEditing = true;
            }
        }
    }


    void readText()
    {
        string line;
        cout << "\nEnter the name of your file: ";
        cin >> siData.fileName;
        cout << "\n";
        const char* path = siData.fileName.c_str();

        // input file stream
        //Internal stream buffer which performes I/O on file.
        ifstream siFileRead(path);
        if(siFileRead.is_open())
        {
            while(getline(siFileRead,line))
            {
                cout << line << endl;
                siData.siFile << line;
            }
        }

        else
        {
            cout << "Unable to open file. Confirm name and file location.";
        }
    }

    // open the existing text file
    void appendTextOpen()
    {
        while (siData.siFileAppend.is_open())
        {
            // erase previous text
            siData.siFileAppend.clear();
            // close the input text file
            siData.siFileAppend.close();
        }

        cout << "\nEnter the name of the file: ";
        //find file name.
        cin >> siData.appendTextfileName;

        //Makes / Opens file
        const char* path = siData.appendTextfileName.c_str();
        siData.siFileAppend.open(path, fstream::app);
    }

    //add text together with previous input.
    void appendText()
    {
        bool editing = true;
        bool hasEditing = false;

        while (editing == true)
        {
            //Gets user input
            string input = " ";
            getline(cin, input);

            if (input == "=!")
            {
                if (hasEditing == true)
                {
                    cout << "File saved: " << siData.appendTextfileName.c_str() << endl;
                    editing = false;
                }
            }

            else
            {
                siData.siFileAppend << input;
                siData.siFileAppend << endl;
                hasEditing = true;
            }
        }
    }
};

textData.h:

#ifndef SIEDITOR_H
#define SIEDITOR_H

class textData
{
    public: 
        string fileName;
        string appendTextfileName;
        ofstream siFile;
        ofstream siFileAppend;
};

#endif

textEditor.h:

#ifndef SIEDITOR_H
#define SIEDITOR_H

class textEditor
{
    public: 
    void openText()
    void writeText()
    void readText()
    void appendTextOpen()
    void appendText()
};


#endif

Upvotes: 1

Views: 3879

Answers (3)

oklas
oklas

Reputation: 8240

Class must be defined only once.

Move class definition to separated header file (join togather both content of your same name class: fields and methods):

// textEditor.h
#pragma once
class textEditor {
  void appendText();
private:
    string fileName;
}

Move class methods to separate source file:

// textEditor.cpp
#include "textEditor.h"
void textEditor::appendText() {
   // ... impl
}

And in the main.cpp:

// main.cpp
#include "textEditor.h"

textEditor siEditor;

int main() 
{
  siEditor.appendText();
}

Upvotes: 1

Christian Hackl
Christian Hackl

Reputation: 27538

Consider what the preprocessor does. It runs separately for every *.cpp file and processes all your #include, #ifndef, #define and #endif statements.

This is the start of the your main.cpp:

#include <iostream>
#include <fstream>

using namespace std;

#include "textData.h"
#include "textEditor.h"

textData siData;
textEditor siEditor;

How would you preprocess this if you were a preprocessor?[*] You would probably start with the #include statements. The intermediate result would be:

// contents of <iostream>...
// contents of <fstream>...

using namespace std;

#ifndef SIEDITOR_H
#define SIEDITOR_H

class textData
{
    public: 
        string fileName;
        string appendTextfileName;
        ofstream siFile;
        ofstream siFileAppend;
};

#endif

#ifndef SIEDITOR_H
#define SIEDITOR_H

class textEditor
{
    public: 
    void openText()
    void writeText()
    void readText()
    void appendTextOpen()
    void appendText()
};


#endif

textData siData;
textEditor siEditor;

Now let's inspect this intermediate result:

// contents of <iostream>...
// contents of <fstream>...

using namespace std;

#ifndef SIEDITOR_H // <--- true, SIEDITOR_H is not defined, don't skip until #endif
#define SIEDITOR_H // <--- OK, SIEDITOR_H is now defined

class textData
{
    public: 
        string fileName;
        string appendTextfileName;
        ofstream siFile;
        ofstream siFileAppend;
};

#endif // <--- end of block started by #ifndef SIEDITOR_H

#ifndef SIEDITOR_H // <--- false, SIEDITOR_H is defined, skip until #endif
#define SIEDITOR_H

class textEditor
{
    public: 
    void openText()
    void writeText()
    void readText()
    void appendTextOpen()
    void appendText()
};


#endif // <--- end of block started by #ifndef SIEDITOR_H

textData siData;

The result of the preprocessing turns out to be:

// contents of <iostream>...
// contents of <fstream>...

using namespace std;

class textData
{
    public: 
        string fileName;
        string appendTextfileName;
        ofstream siFile;
        ofstream siFileAppend;
};

textData siData;
textEditor siEditor; // error, unknown type textEditor

This explains the specific error message you have been asking about. The solution is to use a different include guard in every header file. You must choose completely unique include guard names. In big projects, this can become difficult. Here's some reading material:


Nevertheless, there are more errors in your code:

First of all, your header files assume a lot. They assume that someone else already included the necessary standard headers to get std::string and std::ofstream. They also assume that someone else has already used using namespace std; or using std::string; using std::ofstream;.

That's very bad practice. Your header file should include the standard headers it needs, and just spell out the complete names (no using namespace std;).

In addition to that, it should use standard headers which are guaranteed to contain what you need. If you need std::string, then include <string>.

Standard headers may include other standard headers, but there are only very few guaranteed indirect inclusions (I am too lazy to look up in the standard if <iostream> implies <string>, but I guess it does not.)

Here is an example:

textEditor.h:

#ifndef SI_TEXT_DATA_H
#define SI_TEXT_DATA_H

#include <string>
#include <fstream>

class textData
{
    public: 
        std::string fileName;
        std::string appendTextfileName;
        std::ofstream siFile;
        std::ofstream siFileAppend;
};

#endif

Finally, you redefine your textEditor class in siEdit.cpp. That's not allowed. You should instead declare the member functions in the *.h file inside of the class definition, and define the member functions in the *.cpp file.

textEditor.h should look like this:

#ifndef SI_TEXT_EDITOR_H
#define SI_TEXT_EDITOR_H

class textEditor // class definition begins
{
    public: 
    void openText();       // declaration of member function
    void writeText();      // declaration of member function
    void readText();       // declaration of member function
    void appendTextOpen(); // declaration of member function
    void appendText();     // declaration of member function

}; // class definition ends

#endif

And siEdit.cpp should look like this:

#include "textData.h"
#include "textEditor.h"

textData siData;

void textEditor::openText() // definition of a member function begins
{   
    // ...

} // definition of a member function ends

// other definitions

Your global variables (like textData siData;) are not a very good idea, either, especially seeing as how they are not wrapped in anonymous namespaces.


[*] An actual preprocessor may not technically work like this, but you can picture it that way.

Upvotes: 0

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145389

You're using the same include guard in both header files, namely SIEDITOR_H. That prevents the second header's contents from being included. Use #pragma once instead of include guard symbols.

#pragma once is a de facto standard that's supported by all compilers of practical interest.

In your implementation file don't repeat the class definition. Just define the declared member functions. And static data members, if any.

Upvotes: 1

Related Questions