0

So I am writing a C++ program that will mimic a library card catalog. I defined a struct for card and all the info on each card, as well as a working vector and iterator to access/print all variables on specified card using a global void function.

Now, I want to move that void function within a newly defined struct, Catalog that handles all the methods for dealing with library cards, like insert/push_back, search or remove/erase/pop_back. I also want my variables under card to be protected, as I'm constantly told it's good coding practice to have your class/struct variables to be private (I did protected for other classes inherited).

//#include <cstdio>
#include <iostream>
//#include <stdio.h>
#include <vector>
#include <string>
using namespace std;

struct Card
{
public: 
    Card(string title, string name)
    {
        this->title = title;
        this->name = name;
    }
//protected:
    string title = "Unknown";
    string name = "Unknown";
};

vector<Card> test;
vector<Card>::iterator it;

void showCard(vector<Card> test)
{
    for (it = test.begin(); it != test.end(); it++)
    {
        if (it->title != "Unknown")
        {
            printf("%s\n", it->title.c_str());
            printf("%s\n", it->name.c_str());
        }
    }
}

int main()
{
    Card book1 = { "Serpent in the heather / Kay Kenyon", "Kay Kenyon"};
    Card book2 = { "USA and the Middle East since World War 2 / 
    T.G. Fraser.", "T.G. Fraser"};
    Card book3 = { "My Horse and wally", "Jason Weber" };

    test.push_back(book1);
    test.push_back(book2);
    test.push_back(book3);

    showCard(test);

    getchar();
    return 0;
}

I guess my question is, how could I call Catalog struct from main to then access the protected variables under Card in order to print the protected variables?

Couldn't be as simple as listing friend struct Card in catalog would it?

Edit: I played around and found that friend struct Catalog under Card was able to get rid of errors in the void function for the protected variables it tried accessing. I still am working to have main pass through catalog, though I had all objects in main defined as Card.

I suppose I could try a setCard() called in main, defined in Catalog where it uses vector to refer to the variables protected.

obidyne
  • 1
  • 2
  • 1
    If you want to "protect" the members of your class, you could provide `getters` and `setters` to your members. See https://stackoverflow.com/questions/760777/c-getters-setters-coding-style for details. – Mine Sep 28 '18 at 02:19

2 Answers2

2

There are multiple ways to do it, and the right way depends on the context. Here are some possible solutions, from the easiest/hackiest to the most verbose/hardest (not an exhaustive listing):


1. Just make everything public

...

struct Card{
    public: 
    Card(string title, string name){
        this->title = title;
        this->name = name;
    }
    string title = "Unknown";
    string name = "Unknown";
};

...

void showCard(vector<Card> test){
    for (it = test.begin(); it != test.end(); it++){
            if (it->title != "Unknown"){
                    printf("%s\n", it->title.c_str());
                    printf("%s\n", it->name.c_str());
                }
        }
}

While that does solve the problem, it isn't a good solution. If you would ever want to change the name of member title to main_title you will have quite a pain in doing so, because you will have do edit every single occurrence of title and that can get messy quickly.


2. Make void showCard(vector<Card> test) a friend of struct Card

If void showCard(vector<Card> test) is friend of Card then it will have access to all protected and private members of Card as if they were public. This is a nice solution because only void showCard(vector<Card> test) would have access to these protected members.

Because you can only be friend of previously declared functions, you would need to forward declare the function void showCard(vector<Card> test) before the declaration of Card.

However, because void showCard(vector<Card> test) takes a vector<Card> argument, the class Card needs to be forward declared before the forward declaration of the function.

...
struct Card;
void showCard(vector<Card> test);

struct Card{
    public: 
    friend void showCard(vector<Card> test);
    Card(string title, string name){
        this->title = title;
        this->name = name;
    }
    protected:
    string title = "Unknown";
    string name = "Unknown";
};

...
void showCard(vector<Card> test){
    for (it = test.begin(); it != test.end(); it++){
            if (it->title != "Unknown"){
                    printf("%s\n", it->title.c_str());
                    printf("%s\n", it->name.c_str());
                }
        }
}

3. Create getters and setters for Card

This is one of the canonical implementations. Every time you make a member private/protected you provide a get_member and a set_member methods for it.

That way everyone can access the member, however, they only can access it if they use those methods. You can even create getters/setters for members that don't exist (i.e. you compute them when you need them).

Since the code speaks more than words, here is an implementation:

...

struct Card{
    protected:
    string title = "Unknown";
    string name = "Unknown";
    
    public: 
    Card(string title, string name){
        this->title = title;
        this->name = name;
    }

    string get_title(){
        return this->title;
    }
    void set_title(string new_title){
        this->title = new_title;
    }
    string get_name(){
        return this->name;
    }
    void set_name(string new_name){
        this->name = new_name;
    }
};

...

void showCard(vector<Card> test){
    for (it = test.begin(); it != test.end(); it++){
            if (it->get_title() != "Unknown"){
                    printf("%s\n", it->get_title().c_str());
                    printf("%s\n", it->get_name().c_str());
                }
        }
}

If you ever would want to change the name of the member title to main_title, you would only have to edit get_title and set_title and all your code would keep working as if you didn't change it at all. You could even delete that member or do anything else (like fetching it from a database) because the only place where it's existence and name matters is inside get_title and set_title. Without getters and setters, you would need to edit every single occurrence of title in order to do that.

Getters and setters are also wonderful places to improve the const correctness of your code, making it more robust and efficient. A const-correct get/set pair would look something like this:

const string& get_title() const {
    return this->title;
}

void set_title(const string& new_title){
    this->title = new_title;
}

And a pair for a non-existent member would look like this:

#include <string>
#include <algorithm>
#include <iterator>

string get_title_and_name(){
    // Concatenates the title and name
    return this->title + " / " + this->name;
}

void set_title_and_name(string new_string){
    // Splits the string between a title and a name
    std::size_t split_point = 0;
    split_point = new_string.find('/');
    this->title = new_string.substr(0, split_point);
    // We don't want to include the char '/' of
    // the new_string in this->name, so use
    // (split_point + 1) instead of split_point
    this->name = new_string.substr(split_point + 1, new_string.size() - (split_point + 1));
}

While this solution may be more verbose than others, it is also more flexible.


A suggested solution

We could modify solution 3 by creating a new struct Catalog and putting void showCard(vector<Card> test) inside it. This isn't a usual solution, put it open the possibility for we to get rid of some global variables (global variables are almost always evil) and hide the fact that we are using a vector<Card> to keep Cards (we could use a hashmap instead of a vector and that would work as well, so that other code don't need to know which one we chose of the two).

//#include <cstdio>
#include <iostream>
//#include <stdio.h>
#include <vector>
#include <string>
using namespace std;


// As in solution 3
struct Card {
protected:
    string title = "Unknown";
    string name = "Unknown";
public: 
    Card(string title, string name){
        this->title = title;
        this->name = name;
    }

    // Right now we only need getters,
    // but we could have setters as well
    // (the names are in camelCase to follow
    //  showCard() naming convention)
    string getTitle(){
        return this->title;
    }
    string getName(){
        return this->name;
    }
};


struct Catalog {
    protected:
    // This one was a global variable previously
    // Also we don't specify a default value
    // for it here, we will do that in the constructor
    vector<Card> test;

    public:
    Catalog(){
        // The start value of test will be a empty vector
        this->test = vector<Card>();
    }

    // We moved void showCard(vector<Card> test) to here
    void showCard(){
        // This is a local variable now
        vector<Card>::iterator it;

        // For loop as in solution 3
        for (it = this->test.begin(); it != this->test.end(); it++){
                if (it->getTitle() != "Unknown"){
                        printf("%s\n", it->getTitle().c_str());
                        printf("%s\n", it->getName().c_str());
                    }
            }
    }

    // A new method for adding cards,
    // because external code shouldn't care
    // about how we add or remove card or even
    // if we store cards in this machine or in a web server
    void addCard(Card card){
        this->test.push_back(card);
    }
};

int main()
{
    Card book1 = { "Serpent in the heather / Kay Kenyon", "Kay Kenyon"};
    Card book2 = { "USA and the Middle East since World War 2 / T.G. Fraser.", "T.G. Fraser"};
    Card book3 = { "My Horse and wally", "Jason Weber" };
    Catalog catalog;


    catalog.addCard(book1);
    catalog.addCard(book2);
    catalog.addCard(book3);

    // We could even do something like
    // catalog.addCard({ "My Horse and wally", "Jason Weber" });
    // thankfully to the new addCard method.
    // We wouldn't even need to declare book1, book2 and book3
    // if we used it that way

    catalog.showCard();

    getchar();
    return 0;
}

After you end writing your program, you may be interested in showing it on Code Review in order to receive insights about how other people would approach the same code and learn how people more experienced or with a different background would write such code.

Community
  • 1
  • 1
1

@obidyne, welcome to StackOverflow. If your goal is to keep the members protected but still be able to show them off (as a formatted string), you could implement a public method showCard, rename your other function showCards and call the public method for each object of the vector.

Just an example (using your own code):

//#include <cstdio>
#include <iostream>
//#include <stdio.h>
#include <vector>
#include <string>
using namespace std;

struct Card
{
public: 
    Card(string title, string name)
    {
        this->title = title;
        this->name = name;
    }
    void showCard()
    {
        if (this->title != "Unknown")
        {
            printf("%s\n", this->title.c_str());
            printf("%s\n", this->name.c_str());
        }
    }
protected:
    string title = "Unknown";
    string name = "Unknown";
};

vector<Card> test;
vector<Card>::iterator it;

void showCards(vector<Card> test)
{
    for (it = test.begin(); it != test.end(); it++)
    {
        it->showCard();
    }
}

int main()
{
    Card book1 = { "Serpent in the heather / Kay Kenyon", "Kay Kenyon"};
    Card book2 = { "USA and the Middle East since World War 2 / 
    T.G. Fraser.", "T.G. Fraser"};
    Card book3 = { "My Horse and wally", "Jason Weber" };

    test.push_back(book1);
    test.push_back(book2);
    test.push_back(book3);

    showCards(test);

    getchar();
    return 0;
}
RLThomaz
  • 111
  • 2
  • 4
  • I just visualized your code and realized, oh yeah the person actually flips through the cards themselves lol I think since vector `test` is Global, we might not need any input parameters for `showCards`, would we? Also, I get an error saying showCard within `it->showCard();` is not a member of `struct Card` – obidyne Sep 28 '18 at 03:14
  • I realized I moved `void showCard()` to my new `struct Catalog` instead. Do I even need a Catalog struct class? I can just have one structure called card and all the catalogues can simply be vectors, right? – obidyne Sep 28 '18 at 03:24
  • Well, you should not rely on Globals. And a "Catalog" struct could be useful for implementing other methods, e.g. `showCards`. Imagine that `Card` is for a game of cards, would it make sense to create a `Deck`? I strongly believe it would. But, of course, you have to analyze your needs and how you can improve your classes to make your code better and thus your life easier. – RLThomaz Sep 28 '18 at 03:31
  • good thinking; could save these structs in a header file to save for a poker program lol for now I need to get a working catalog to find and print library book info. Got any god suggestions for calling a member of Catalog struct from main or public functions? – obidyne Sep 28 '18 at 03:47
  • I got a Bug Assertion failed message, now the program won't even run. It reads: can't dereference value-initialized vector iterator – obidyne Sep 28 '18 at 04:13
  • There are several ways of calling members from structs and classes, for exemple, directly (public members), using methods, getters and setters, or even operators. – RLThomaz Sep 28 '18 at 04:15
  • This happened after getting errors and realizing my global vectors were declared after my card struct. I then moved the vector declarations near the top of the program above card structure. After debugging, I got the Failure message. – obidyne Sep 28 '18 at 04:43
  • Fixed the assertion failure: decided to just declare a new iterator under each function and method that called for it, and made sure to pass `it` to the ones without the for loop – obidyne Sep 28 '18 at 05:31
  • Again, do not use Globals. Why creating a Global iterator? Just use an `auto it` on your `for` loops. E.g., `for (auto it = test.begin(); it != test.end(); it++) { /* Do stuff */ }` – RLThomaz Sep 28 '18 at 06:49
  • Good call on `auto`. I've never actually used it before. – obidyne Sep 28 '18 at 07:30
  • @RLThomas if not Global, where would you put the vector(s) that hold all the book cards? – obidyne Sep 28 '18 at 08:58
  • Exactly where you are using them, the `main` function. You do understand that `vector test` is not being used by anything else but the `main` function, right? If you are following Gabriel Francischini's answer you can see that you could create it inside your `Catalog` struct. – RLThomaz Sep 28 '18 at 09:02