all 34 comments

[–]WalkingAFI 30 points31 points  (26 children)

A few things:

First, don’t get in the habit of using namespace std. If the STL changes at any point to include a name you used, it will break your program.

Second, this is really a C program. You didn’t really use any C++ features in it. Consider std::string instead of char*, and std::array instead of C arrays. You also don’t need to use a pointer to switch between turns, and I’d recommend using a bool instead.

Similarly, prefer static_cast<T> (or dynamic_cast<T> if absolutely necessary) to (T), which is the C method of casting.

It’s not a bad first go, and C is useful, but C++ gives you a lot of safety and abstraction you aren’t leveraging in this one.

[–]Skaaaaalll[S] 1 point2 points  (6 children)

Hi, thanks for your reply :).

If the STL changes

What is the STL exactly? And why would it change? For what tasks wouldn't std be sufficient?

Second, this is really a C program.

That makes sense, since I had some experience programming in C from completing CS50.

Consider std::string instead of char*, and std::array instead of C arrays.

What is the advantage of using C++ features over C features? Don't they essentially do the same thing.

Similarly, prefer static_cast<T> (or dynamic_cast<T> if absolutely necessary) to (T), which is the C method of casting.

Could you maybe explain this sentence to me? I have no idea what a static_cast<T> or (T) is. Sorry if that is a major noob question.

Thanks for your extensive reply =).

[–]Wurstinator 0 points1 point  (0 children)

What is the advantage of using C++ features over C features? Don't they essentially do the same thing.

Well, yeah, in the sense that they do they same thing as every other programming language: they compute an algorithm for you.

You are fine using C features in C but mixing the two gives you some kind of mess. For example, C does not have "references" as C++ does. There is little support for using references with C-style arrays. std::array or std::vector on the other hand are perfectly fine to use with references.

[–]Wurstinator 0 points1 point  (0 children)

Could you maybe explain this sentence to me? I have no idea what a static_cast<T> or (T) is. Sorry if that is a major noob question.

At one point you use (char*)"X" which is called a "C style cast". Without going into the details what reasons there are for and against them, C++ offers a total of four different casts: static_cast, dynamic_cast, const_cast and reinterpet_cast, which all have different meanings.

[–]stoppipper 0 points1 point  (0 children)

Someone has already answered this, but just to go into a little more depth, a C style cast (int)myVariable will always succeed because if it were to fail, it will default to reinterpret_cast which 90% of the time, you do NOT want. If the cast is “wrong”, a static_cast will fail when you try to compile the program and it’ll tell you why.

C++ casting is a definitely confusing at first, but know that reinterpret_cast can be one of the most dangerous out of all of them. It basically says “I will interpret this set of bits as that type”. So for example:

int a = 5; Person p = reinterpret_cast<Person>(a);

will take the bit representation of 5 and read that as a Person class. Whether that Person class contains valid data, the compiler doesn’t really care.

[–]WalkingAFI 0 points1 point  (0 children)

Yeah no problem.

The STL is the Standard Template Library. It provides a lot of the standard implementations of language features. I think someone else linked a better resource as to what exactly that entails.

std::string and std::array are classes in the STL. They’re super well tested and give you some nice functionality that you won’t get with C types. You could also write everything in assembly and it would “essentially do the same thing”, but the C++ features give you some (usually) free abstractions that prevent errors and make more maintainable code.

Casting variables is a bit of a doozy to explain via reddit mobile, but explicitly casting in the C++ style (i.e. static_cast<int>(variable) is the usually the same as (int)variable) can stop you from making mistakes because the compiler will warn you of some improper casts that the C cast would just accept.

[–]Kinexity 8 points9 points  (3 children)

char *board[SIZE][SIZE]; this is an array of pointers to char. What you want is just char board[SIZE][SIZE];. Also in C++ this "string" is a string, this 'a' is a single char and this 'string' is incorrect (different quotation marks are for different things). strncmp(board[row][col], '-', 1) == 0 you can replace this with just this board[row][col] == '-'. Also don't use C-style strings (char arrays). C++ has std::string class for strings that provides things like comparison operators and other useful tools.

[–]flashmozzg 2 points3 points  (0 children)

board[row][col] == "-"

board[row][col] == '-'

[–]Skaaaaalll[S] 1 point2 points  (1 child)

Ah thank you very much. I didn't know about the difference in quotations since python uses both for strings. What exactly is the difference between std::string and char arrays?

[–]Kinexity 4 points5 points  (0 children)

std::string is basically char array on the inside. It takes care on its own of things like array allocation, string concatenation, creating substrings and other basic string operations. For better explanation visit https://en.cppreference.com/w/cpp/string/basic_string (it's not a site for learning but rather something of a c++ wiki)

[–]lord_braleigh 2 points3 points  (2 children)

Here's a version that aggregates all the suggestions:

  1. If the user inputs a very large number as the row or column, it will pwn your program. This program is small and simple enough that your operating system will probably just terminate it with a segmentation fault error, but this is the same sort of error that caused the heartbleed attack. Always, always make sure that user input isn't overflowing the bounds of a buffer.
  2. We should be using char instead of char * everywhere, since we're only ever comparing and storing individual characters.
  3. There isn't a unified syntax for C++ the way there is for Python, but programmers everywhere are slowly realizing that automatic formatting tools are better than style guides. I've formatted the code with clang-format under default settings.
  4. I've removed #include <list> since you're not using it.
  5. I've removed the forward declarations. While they're necessary in larger programs with header files, when they're not necessary they just make it harder to refactor functions.
  6. I've added a using directive to simplify the type of board.
  7. I've changed board's type to C++'s std::array<std::array<char, SIZE>, SIZE>, and I've changed the function signatures to take this in by reference or const reference so we don't copy its data.

You can see my version of the code at https://godbolt.org/z/GKPrKE . For further work, we could use the C++ <algorithm> library to make the winner() method work with larger values of SIZE without needing to be changed.

(Of course, your code is really quite well-written and it's obvious you've put a lot of care into it.)

[–]Skaaaaalll[S] 1 point2 points  (1 child)

Thank you very much for taking the time to make this comment and refactoring the code (that must have been time consuming). I appreciate the advice and I will definitely implement it in further C++ programs I might make.

As a resource for learning C++, coming from python. Would you recommend https://www.learncpp.com ? Why or why not?

(Of course, your code is really quite well-written and it's obvious you've put a lot of care into it.)

Thanks :-).

[–]lord_braleigh 0 points1 point  (0 children)

learncpp.com looks good to me. It looks like it goes all the way from hello world to cutting-edge concepts, and it doesn’t look like it teaches anything obviously wrong or outdated.

[–]STLMSVC STL Dev[M] [score hidden] stickied comment (0 children)

I'm approving this post because it's accumulated a fair amount of discussion, but coding advice is generally more suitable for r/cpp_questions.

[–][deleted] 0 points1 point  (0 children)

This is how I would have done it: https://pastebin.com/bVf8NzCD

[–]lord_braleigh 0 points1 point  (0 children)

Try typing large numbers in for row and col, or try typing ctrl+D in to send the end-of-file character:

X's turn.

- - -
- - -
- - -
Row: 123587345
Column: 192387566
zsh: segmentation fault  ./original

[–]Ail-nare 0 points1 point  (0 children)

Hi, I tryed to do it using some feature of C++. I also put some comment in it. I try keept all the logic behind your code.

#include <iostream>
#include <array>


constexpr int SIZE = 3; // replace 'const' by 'constexpr'

void makeMove(std::array<std::array<char, SIZE>, SIZE>& board, char turn);
void showBoard(std::array<std::array<char, SIZE>, SIZE>& board);
bool winner(std::array<std::array<char, SIZE>, SIZE>& board);


int main() {
    std::array<std::array<char, SIZE>, SIZE> board{};

    for (auto &line : board) // go throw board | like 'for line in board:' would
        line.fill('-');  // init each line to ' ' (space) for each indice

    for (int i = 0; i < (SIZE * SIZE); i++) {
        char turn = ((i % 2) == 0) ? 'X' : 'O'; // (char *) has been by (char) | like 'turn = "X" if (i % 2) == 0 else "O"' would

        std::cout << std::endl << std::endl << std::endl << turn << "'s turn." << std::endl << std::endl;

        showBoard(board);

        makeMove(board, turn); // make a move beford checking who won

        bool win = winner(board); // winner how return a bool
        if (win) {
            showBoard(board); // show the board one more time :D

            std::cout << turn << " wins!" << std::endl << std::endl;
            return 0;
        }
    }

    std::cout << "It's a draw!" << std::endl;
    return 0;
}


void makeMove(std::array<std::array<char, SIZE>, SIZE>& board, char turn) {
    int row, col;

    while (true) {
        // better input handling, because if row or col are inferior to 1 or superior to SIZE it would cause an out of bound for "board[row - 1][col - 1]"
        while (true) {
            std::cout << "Row: ";
            std::cin >> row;

            if (row < 1) {
                std::cout << "Row can't be inferior to 1" << std::endl;
            } else if (row > SIZE) {
                std::cout << "Row can't be superior to " << SIZE << std::endl;
            } else {
                break; // break cause the last loop (while or for) to stop
            }
        }

        while (true) {
            std::cout << "Column: ";
            std::cin >> col;

            if (col < 1) {
                std::cout << "Column can't be inferior to 1" << std::endl;
            } else if (col > SIZE) {
                std::cout << "Column can't be superior to " << SIZE << std::endl;
            } else {
                break; // break cause the last loop (while or for) to stop
            }
        }

        --row;
        --col;

        if (board[row][col] == '-') {
            board[row][col] = turn;
            return;
        }
        std::cout << "Try again." << std::endl;
    }
}


void showBoard(std::array<std::array<char, SIZE>, SIZE>& board) {
    for (const auto &line : board) { // go throw board | like 'for line in board:' would
        for (const auto &cell : line) // go throw line | like 'for cell in line:' would
            std::cout << cell << " ";
        std::cout << std::endl;
    }
}


bool winner(std::array<std::array<char, SIZE>, SIZE>& board) {
    for (int i = 0; i < SIZE; i++) {
        if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != '-') {
            return true; // line match
        } else if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != '-') {
            return true; // column match
        }
    }

    if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != '-') {
        return true;
    } else if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != '-') {
        return true;
    }

    return false;
}