1

Before this project, my only experience with C++ was with some basic arduino programming. I decided to suck it up and start working with C++ for my projects rather than trying to make python work for things it shouldn't, so I'm trying to figure out a lot of best practice things and simple issues.

I have written a program that has many classes inside of it. The classes have functions which do many things, but it may be important to note that some functions start threads or are continually looping inside a thread. I often need to call functions from another class from a different class, but that seems to be causing me issues. What I've been doing now is creating an object for that class I need to reference inside of the class I'm currently in, but that is causing issues with the order I put my classes in my code, for example:

(347,13): error C2079: 'anim' uses undefined class 'animation'

This error occurs in this section of the code:

class metronome {

private:
    changeTempo tempo;
    animation anim;

    int lastManualTap = 0;

    void beat() {

        if (started) {
            std::cout << "--------  " << j << "\n";
            j++;
            if (j == (globalBeatsPerMeasure + 1)) { j = 1; }
        }
        else {
            std::cout << "--------\n";
        }
    }

    //runs the click at a set tempo without user input
    void autoMetro(float bpm) {
        
        //global variable to stop the auto metro loop
        while (autoActive) {

            int bpmdelay = (60.0 / bpm) * 1000;
            if ((clock() - lastBeatTime) >= bpmdelay) {

                subdivideActive = false;
                isFirst = false;

                beat(); //console output
                startSub(bpmdelay); //start subdivion
                tempo.setBpm(bpm); //sets auto tempo to manual tempo
                lastBeatTime = clock();
                lastAutoBeatTime = clock();


            }
        }
        return;
    }


    // outputs the subdivision at a set number of subdivisions per beat
    void subdivide(int timeBetweenBeats) {

        int timeBetweenSubdivision = timeBetweenBeats / globalSubdivision;
        int b = 2;
        int lastSubdivisionTime = clock();

        subdivideActive = true;
        while (subdivideActive == true) { // makes sure the subdivison is supposed to be on in case there is a manual tap before end of subdivision

            //check if the amount of time between subdivisions has passed, print subdivision
            if ((clock() - lastSubdivisionTime) >= timeBetweenSubdivision) {
                lastSubdivisionTime = clock();
                std::cout << b << "\n";
                b++;
            }

            //when completed with set amount of subdivisions per beat, break
            if (b == (globalSubdivision + 1)) {
                b = 2;
                subdivideActive = false;
            }

        }

    }

    //start subdivision
    void startSub(int delay) {
        std::thread subthread(&metronome::subdivide, this, delay);
        subthread.detach();
    }

    //sdfusdhfus this is all trash anyways
    void manualMetro() {

        char c;
        while (1) {
            if (fileLoaded) {

                c = _getch();
                if (c) {

                    if (c == 'b') {


                        if ((clock() - lastAutoBeatTime) >= 300) {

                            subdivideActive = false;
                            beat();
                            this->startSub(clock() - lastBeatTime);

                            if (!isFirst) {
                                globalBPM = tempo.averageBpm((clock() - lastManualTap));
                            }
                            isFirst = false;

                        }


                        lastManualTap = clock();
                        lastBeatTime = clock();
                        autoActive = false;

                    }
                    else if (c == ' ' && autoActive == false) {

                        started = true;
                        subdivideActive = false;
                        this->startAutoMetroThread(globalBPM);

                    }
                    else if (c == 'r' && started == true) {

                        anim.initializeAnimationFile("animation.txt");

                    }
                }
            }
        }

    }

    // start the auto metro thread
    void startAutoMetroThread(float bpm) {
        autoActive = true;
        std::thread autometrothread(&metronome::autoMetro, this, bpm);
        autometrothread.detach();
    }


public:


    // start thread to control the metronome
    void startManualMetrothread() {
        std::thread keyboard(&metronome::manualMetro, this);
        keyboard.detach();
    }


};


//class that reads from the animation file
class animation {


private:
    metronome metro;
    std::string line;




public:

    void initializeAnimationFile(std::string animationFileName) {

        std::ifstream animationFile(animationFileName);

        autoActive = false;
        started = false;
        subdivideActive = false;

        if (animationFile.is_open())
        {
            while (std::getline(animationFile, line))
            {
                if (line.rfind("#", 0) == 0) {

                    if (line.rfind("#=", 0) == 0) {
                        line.erase(0, 5);
                        globalBPM = std::stof(line);
                        std::cout << globalBPM << " BPM\n";
                    }

                    //set bpm
                    else if (line.rfind("#bpm=", 0) == 0) {
                        line.erase(0, 5);
                        globalBPM = std::stof(line);
                        std::cout << globalBPM << " BPM\n";
                    }

                    //set beats per measure
                    else if (line.rfind("#beatsPerMeasure=", 0) == 0) {
                        line.erase(0, 17);
                        globalBeatsPerMeasure = std::stoi(line);
                        std::cout << globalBeatsPerMeasure << " beats per measure\n";
                    }

                    //set subdivision
                    else if (line.rfind("#subdivision=", 0) == 0) {
                        line.erase(0, 13);
                        globalSubdivision = std::stoi(line);
                        std::cout << globalSubdivision << " subdivisions\n";
                    }

                    //signal that the settings have been located
                    else if (line.rfind("#start", 0) == 0) {
                        fileLoaded = true;
                        std::cout << "Ready!\n";
                    }


                    else {
                        std::cout << line << " command not recognized!\n";
                    }

                }
                else if (line.rfind("&", 0) == 0) {



                }
            }
            animationFile.close();
        }
    }


    
};

This is my full code:

#undef UNICODE

#define WIN32_LEAN_AND_MEAN


#include <time.h>
#include <stdio.h>
#include <iostream>
#include <thread>
#include <conio.h>
#include <atomic>
#include <fstream>
#include <string>
#include <sstream>
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>

//intialize global variables between threads
std::atomic<float> globalBPM(0);
std::atomic<int> globalSubdivision(0);
std::atomic<int> globalBeatsPerMeasure(0);
std::atomic<bool> isFirst(true);
std::atomic<bool> started(false);
std::atomic<bool> fileLoaded(false);
std::atomic<bool> autoActive(false);
std::atomic<int> lastAutoBeatTime(0);
std::atomic<int> lastBeatTime(0);
std::atomic<int> j(1);

std::atomic<bool> subdivideActive(false);

#define TCPListenerPort "17"


// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")

#define DEFAULT_BUFLEN 512
int iResult;

//array to hold ip addresses of each node at their id value
std::string nodeIP[100] = { "0" };

//array to hold battery voltage of each node at their id value
std::string nodeBatt[100] = { "0" };

class animation;

//class that handles all network transfers
class network {


private:
    char recvbuf[DEFAULT_BUFLEN] = { 0 };
    int recvbuflen = DEFAULT_BUFLEN;
    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET ClientSocket = INVALID_SOCKET;
    SOCKET Listen;

    //thread that handles recieved connections from nodes
    void TCPRequestRecvThread(SOCKET sock) {

        do {

            //get data from node, save it to string, output it
            iResult = recv(sock, recvbuf, recvbuflen, 0);
            if (iResult > 0) {
                std::string str = recvbuf;
                //printf("Bytes received: %d\n", iResult);
                std::cout << str << "\n";
            }

            else if (iResult == 0) {
            //  printf("Connection closing :(((\n");
            }

            else {
                printf("recv failed with error: %d D:\n", WSAGetLastError());
                closesocket(sock);
                WSACleanup();
            }

        } while (iResult > 0);

        //exit thread
        return;
    }

    //start a thread to handle the node connection
    void startTCPRequestRecvThread(SOCKET sk) {
        std::thread t(&network::TCPRequestRecvThread, this, sk);
        t.detach();
    }

    //loop thread that blocks until client connects and passes socket to recieve thread
    void TCPListener(SOCKET Listen) {

        while (true) {

            // block and accept a client socket
            ClientSocket = accept(Listen, NULL, NULL);
            if (ClientSocket == INVALID_SOCKET) {
                printf("accept failed with error: %d\n", WSAGetLastError());
                WSACleanup();
            }

            //start communication thread
            this->startTCPRequestRecvThread(ClientSocket);
        }
    }

public:

    //start the TCP listener loop thread
    void startTCPListenerThread(SOCKET sk) {
        std::thread t(&network::TCPListener, this, sk);
        t.detach();
    }

    SOCKET intializeTCPSocket() {

        std::cout << "initil start\n";

        struct addrinfo* result = NULL, * ptr = NULL, hints;
        WSADATA wsaData;
        //network tcp;
        int iResult;


        ZeroMemory(&hints, sizeof(hints));
        hints.ai_family = AF_INET;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
        hints.ai_flags = AI_PASSIVE;

        // Initialize Winsock
        iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (iResult != 0) {
            printf("WSAStartup failed with error: %d\n", iResult);
            return INVALID_SOCKET;
        }

        ZeroMemory(&hints, sizeof(hints));
        hints.ai_family = AF_INET;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
        hints.ai_flags = AI_PASSIVE;

        // Resolve the local address and port to be used by the server
        iResult = getaddrinfo(NULL, TCPListenerPort, &hints, &result);
        if (iResult != 0) {
            printf("getaddrinfo failed: %d\n", iResult);
            WSACleanup();
            return INVALID_SOCKET;
        }

        // Create a SOCKET for connecting to server
        ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
        if (ListenSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            freeaddrinfo(result);
            WSACleanup();
            return INVALID_SOCKET;
        }

        // Setup the TCP listening socket
        iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            printf("bind failed with error: %d\n", WSAGetLastError());
            freeaddrinfo(result);
            WSACleanup();
            return INVALID_SOCKET;
        }

        freeaddrinfo(result);

        //start listening on that socket
        iResult = listen(ListenSocket, SOMAXCONN);
        if (iResult == SOCKET_ERROR) {
            printf("listen failed with error: %d\n", WSAGetLastError());
            closesocket(ListenSocket);
            WSACleanup();
            return INVALID_SOCKET;
        }

        //return the setup listening socket
        return ListenSocket;

    }
};



//class that controls tempo changes
class changeTempo {


private:
    int tapTimes[5] = { 0,0,0,0,0 };
    int shiftArray[4];
    int x = 0;
    int add = 0;

public:

    //change the average bpm when there is a manual user input
    float averageBpm(int differance) {

        //moves all the data down one position in the array so only 4 taps are averaged
        if (x == 5) {
            shift();
            x = 4;
        }

        //store time between taps
        tapTimes[x] = differance;
        x++;

        //add all the times together
        add = 0;
        for (int i = 0; i < 5; i++) {
            add = add + tapTimes[i];
        }

        //find the number of user inputted taps in the array (so the bpm average is correct even if the user hasn't put in 4 beats before turning it to auto mode)
        int num = 0;
        for (int i = 0; i < 5; i++) {
            if (tapTimes[i] > 0) { num++; }
        }

        //calculate and return bpm
        return ((float)60 / (add / num))*1000;


    }

    //shift all the time between beats one position down (i know i'm lazy)
    void shift() {

        shiftArray[0] = tapTimes[1];
        shiftArray[1] = tapTimes[2];
        shiftArray[2] = tapTimes[3];
        shiftArray[3] = tapTimes[4];

        tapTimes[0] = shiftArray[0];
        tapTimes[1] = shiftArray[1];
        tapTimes[2] = shiftArray[2];
        tapTimes[3] = shiftArray[3];


    }

    //set all of the times in the array to the auto time once auto is started
    void setBpm(float bpm) {

        for (int i = 0; i < 4; i++) {
            tapTimes[i] = (60.0 / bpm) * 1000;
        }

    }
};




class metronome {

private:
    changeTempo tempo;
    animation anim;

    int lastManualTap = 0;

    void beat() {

        if (started) {
            std::cout << "--------  " << j << "\n";
            j++;
            if (j == (globalBeatsPerMeasure + 1)) { j = 1; }
        }
        else {
            std::cout << "--------\n";
        }
    }

    //runs the click at a set tempo without user input
    void autoMetro(float bpm) {
        
        //global variable to stop the auto metro loop
        while (autoActive) {

            int bpmdelay = (60.0 / bpm) * 1000;
            if ((clock() - lastBeatTime) >= bpmdelay) {

                subdivideActive = false;
                isFirst = false;

                beat(); //console output
                startSub(bpmdelay); //start subdivion
                tempo.setBpm(bpm); //sets auto tempo to manual tempo
                lastBeatTime = clock();
                lastAutoBeatTime = clock();


            }
        }
        return;
    }


    // outputs the subdivision at a set number of subdivisions per beat
    void subdivide(int timeBetweenBeats) {

        int timeBetweenSubdivision = timeBetweenBeats / globalSubdivision;
        int b = 2;
        int lastSubdivisionTime = clock();

        subdivideActive = true;
        while (subdivideActive == true) { // makes sure the subdivison is supposed to be on in case there is a manual tap before end of subdivision

            //check if the amount of time between subdivisions has passed, print subdivision
            if ((clock() - lastSubdivisionTime) >= timeBetweenSubdivision) {
                lastSubdivisionTime = clock();
                std::cout << b << "\n";
                b++;
            }

            //when completed with set amount of subdivisions per beat, break
            if (b == (globalSubdivision + 1)) {
                b = 2;
                subdivideActive = false;
            }

        }

    }

    //start subdivision
    void startSub(int delay) {
        std::thread subthread(&metronome::subdivide, this, delay);
        subthread.detach();
    }

    //sdfusdhfus this is all trash anyways
    void manualMetro() {

        char c;
        while (1) {
            if (fileLoaded) {

                c = _getch();
                if (c) {

                    if (c == 'b') {


                        if ((clock() - lastAutoBeatTime) >= 300) {

                            subdivideActive = false;
                            beat();
                            this->startSub(clock() - lastBeatTime);

                            if (!isFirst) {
                                globalBPM = tempo.averageBpm((clock() - lastManualTap));
                            }
                            isFirst = false;

                        }


                        lastManualTap = clock();
                        lastBeatTime = clock();
                        autoActive = false;

                    }
                    else if (c == ' ' && autoActive == false) {

                        started = true;
                        subdivideActive = false;
                        this->startAutoMetroThread(globalBPM);

                    }
                    else if (c == 'r' && started == true) {

                        anim.initializeAnimationFile("animation.txt");

                    }
                }
            }
        }

    }

    // start the auto metro thread
    void startAutoMetroThread(float bpm) {
        autoActive = true;
        std::thread autometrothread(&metronome::autoMetro, this, bpm);
        autometrothread.detach();
    }


public:


    // start thread to control the metronome
    void startManualMetrothread() {
        std::thread keyboard(&metronome::manualMetro, this);
        keyboard.detach();
    }


};


//class that reads from the animation file
class animation {


private:
    metronome metro;
    std::string line;




public:

    void initializeAnimationFile(std::string animationFileName) {

        std::ifstream animationFile(animationFileName);

        autoActive = false;
        started = false;
        subdivideActive = false;

        if (animationFile.is_open())
        {
            while (std::getline(animationFile, line))
            {
                if (line.rfind("#", 0) == 0) {

                    if (line.rfind("#=", 0) == 0) {
                        line.erase(0, 5);
                        globalBPM = std::stof(line);
                        std::cout << globalBPM << " BPM\n";
                    }

                    //set bpm
                    else if (line.rfind("#bpm=", 0) == 0) {
                        line.erase(0, 5);
                        globalBPM = std::stof(line);
                        std::cout << globalBPM << " BPM\n";
                    }

                    //set beats per measure
                    else if (line.rfind("#beatsPerMeasure=", 0) == 0) {
                        line.erase(0, 17);
                        globalBeatsPerMeasure = std::stoi(line);
                        std::cout << globalBeatsPerMeasure << " beats per measure\n";
                    }

                    //set subdivision
                    else if (line.rfind("#subdivision=", 0) == 0) {
                        line.erase(0, 13);
                        globalSubdivision = std::stoi(line);
                        std::cout << globalSubdivision << " subdivisions\n";
                    }

                    //signal that the settings have been located
                    else if (line.rfind("#start", 0) == 0) {
                        fileLoaded = true;
                        std::cout << "Ready!\n";
                    }


                    else {
                        std::cout << line << " command not recognized!\n";
                    }

                }
                else if (line.rfind("&", 0) == 0) {



                }
            }
            animationFile.close();
        }
    }


    
};


int main()
{ 


    metronome metro;
    animation ani;
    network net;
    SOCKET sk;


    std::cout << "meow\n";
    //initialize the TCP socket and check to see if it was initialized correctly
    sk = net.intializeTCPSocket();
    if (sk == INVALID_SOCKET) {
        std::cout << "Error intializing socket!!\n";
    }

    //start the TCP listener thread
    else {
        net.startTCPListenerThread(sk);
    }

    metro.startManualMetrothread();


    ani.initializeAnimationFile("animation.txt");


    while (true) {}


}






    


I am not sure what is the correct fix for this issue

Vera Fodor
  • 11
  • 2
  • You've only forward declared `animation` when you first try to use it in `anim`. `anim` needs to have seen the definition of `animation` to be used that way. – Ted Lyngmo Jul 03 '22 at 19:33
  • no it is not bad practice. If you could not have class members of class type C++ would be a rather poor language. This explains why you get the error: https://stackoverflow.com/questions/553682/when-can-i-use-a-forward-declaration – 463035818_is_not_an_ai Jul 03 '22 at 19:37
  • So this class design is impossible. Your `animation` class contains an instance of a `metronome` class and vice versa. Remember that these are objects not references or pointers to objects. So you have an infinite recursion of classes. You can make this work if you change one or both classes to contain a pointer to the other class. Whether you should do that or just redesign is hard for me to say. – john Jul 03 '22 at 19:37
  • you wrote far too much code without checking if the code you wrote is ok. This 3 lines are sufficient to see that there is something wrong with your design https://godbolt.org/z/9nK8Pz111 – 463035818_is_not_an_ai Jul 03 '22 at 19:39
  • My program has worked completely fine until I put the `anim.initializeAnimationFile("animation.txt");` into the program, @463035818_is_not_a_number, so I'm confused on your last comment and how it contradicted with your first comment saying that it wasn't bad practice and I just needed to use a forward declaration @463 – Vera Fodor Jul 03 '22 at 19:52
  • @john alternatives to pointers: `unique_ptr`, `shared_ptr`, reference. The first two are preferable because they add resource management so you don't have to. – Goswin von Brederlow Jul 03 '22 at 20:48
  • @VeraFodor Presumably when you added `anim.initializeAnimationFile("animation.txt");` was also when you added `anim` to class `metronome`. This is the fundamental problem, you have mutually recursive classes, and this cannot work ever. It's not a matter of design, it's simply not allowed by C++. – john Jul 04 '22 at 05:56
  • @VeraFodor If you don't want to redesign to break the mutual dependence, then you must change one or both of your classes to contain pointers to the other class (or use a smart pointer as Goswin suggested). But whatever, you have backed yourself into a corner, you must reconsider the direction you are going. – john Jul 04 '22 at 05:58
  • @VeraFodor One class depending on another is OK, mutual dependence is also OK in some circumstances. But two classes which **contain** each other is just illegal. That is the point that 'is_not_a_number' was making. – john Jul 04 '22 at 06:01

0 Answers0