Thread: Using a struct purely for variable documentation

  1. #1
    Registered User
    Join Date
    Jul 2019
    Posts
    34

    Using a struct purely for variable documentation

    I am just writing and playing around with some code and I wanted to have a word represent a number in the parameter list of my class object, my numbers are ints but if i wanted to use a float then I wouldn't be able to use an enum, so I created a struct and then initialized the variable in the parameter lists to keep things neater. Im wondering if this is ok to do. Everything works just fine but i've learned over the years that doesn't always mean it's correct practice.

    Also I was wondering something about const. These variables in the parameter lists of these functions are declared as const:

    Code:
    void TakeDamage(const int& damageTaken);    void RestoreHealth(const int& restoreAmount);
        void UpgradeAttackPower(const int& upgradeAmount);
        void DisplayCharacterInfo(Character& character);
    I was told to make these const by someone else. Now i know that const makes it so the variable cannot be changed, but the damageTaken variable changes so why make them const? why does it still work even though they are declared as const?

    Code:
    #include <iostream>
    #include <string>
    #include <vector>
    
    
    using namespace std;
    
    
    class Item
    {
    public:
        Item();
    
    
        string GetItemName() const { return itemName; }
    
    
    private:
        string itemName{};
        int cost{};
        int healthRestored{};
    };
    
    
    class Character
    {
    public:
        Character(string charClass, int charHealth, int charMaxHealth, int charAttackPwr, int charMaxAttackPwr) :
            charClass(charClass),
            charHealth(charHealth),
            charMaxHealth(charMaxHealth),
            charAttackPwr(charAttackPwr),
            charMaxAttackPwr(charMaxAttackPwr) {}
    
    
        string GetCharClass() { return charClass; }
    
    
        int GetCharHealth() const { return charHealth; }
        int GetCharAttackPwr() const { return charAttackPwr; }
        int GetCharMaxAttackPwr() const { return charMaxAttackPwr; }
        int GetCharMaxHealth() const { return charMaxHealth; }
    
    
        void TakeDamage(const int& damageTaken);
        void RestoreHealth(const int& restoreAmount);
        void UpgradeAttackPower(const int& upgradeAmount);
        void DisplayCharacterInfo(Character& character);
    
    
    private:
        string charClass{};
        int charHealth{};
        int charMaxHealth{};
        int charAttackPwr{};
        int charMaxAttackPwr{};
    };
    
    
    
    
    ostream& operator<< (ostream& os, Character& character)
    {
        os << "Class Name: " << character.GetCharClass() << endl;
        os << "Health: " << character.GetCharHealth() << endl;
        os << "Max Health: " << character.GetCharMaxHealth() << endl;
        os << "Attack Power: " << character.GetCharAttackPwr() << endl;
        os << "Max Attack Power: " << character.GetCharMaxAttackPwr() << endl;
    
    
        return os;
    }
    
    
    void Character::RestoreHealth(const int& restoreAmount)
    {
        if (charHealth + restoreAmount <= charMaxHealth)
        {
            charHealth += restoreAmount;
        }
        if (charHealth + restoreAmount >= charMaxHealth)
        {
            charHealth = charMaxHealth;
        }
    }
    
    
    void Character::TakeDamage(const int& damageTaken)
    {
        if (charHealth > 0)
        {
            charHealth -= damageTaken;
    
    
            if (charHealth <= 0)
            {
                charHealth = 0;
                cout << "The " << GetCharClass() << ", was defeated." << endl;
            }
        }
    }
    
    
    void Character::DisplayCharacterInfo(Character& character)
    {
        cout << character << endl;
    }
    
    
    
    
    //Create a struct to give values a name instead of just a confusing number. 
    //Could have used an enum, but enums cant contain floats, so this fixes that potential issue.
    struct Data
    {
        float health{};
        int maxHealth{};
        int attackPower{};
        int maxAttackPower{};
    };
    
    
    
    
    int main()
    {
        Data data;
    
    
        Character Knight("Knight", data.health = 100, data.maxHealth = 150, data.attackPower = 5, data.maxAttackPower = 5);
        Character Samurai("Samurai", data.health = 50, data.maxHealth = 100, data.attackPower = 7, data.maxAttackPower = 7);
        Character Marine("Marine", data.health = 200, data.maxHealth = 250, data.attackPower = 4, data.maxAttackPower = 4);
    
    
        //Debug
        cout << "--BEGIN DEBUG--\n" << endl;
    
    
        Samurai.TakeDamage(Knight.GetCharAttackPwr());
        Samurai.DisplayCharacterInfo(Samurai);
    
    
        Knight.RestoreHealth(1);
        Knight.DisplayCharacterInfo(Knight);
    
    
        cout << "--END DEBUG--" << endl;
        //End Debug
    
    
        return 0;
    }
    Last edited by ChayHawk; 11-13-2019 at 06:32 AM.

  2. #2
    Registered User
    Join Date
    May 2010
    Posts
    4,632
    Also posted here.

  3. #3
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    You're basically creating an object merely to serve as documentation, and it really is just documentation as you could have just as easily written by mistake:
    Code:
    Character Knight("Knight", data.maxHealth = 150, data.health = 100, data.maxAttackPower = 5, data.attackPower = 5);
    As such, there's a simpler solution to your problem. Comment your code:
    Code:
    Character Knight("Knight", /* health */ 100, /* maxHealth */ 150, /* attackPower */ 5, /* maxAttackPower */ 5);
    If you want to make use of secondary objects to help you avoid mistakes while being self-documenting, then write a configuration class such that you can do something like this:
    Code:
    Character Knight(Character::Config("Knight").health(100).maxHealth(150).attackPower(5).maxAttackPower(5));
    For example:
    Code:
    #include <iostream>
    #include <string>
    #include <vector>
    
    using namespace std;
    
    class Item
    {
    public:
        Item();
    
        string GetItemName() const { return itemName; }
    
    private:
        string itemName{};
        int cost{};
        int healthRestored{};
    };
    
    
    class Character
    {
    public:
        class Config
        {
            friend class Character;
        public:
            explicit Config(const std::string charClass) : charClass(charClass) {}
    
            Config& health(int value)
            {
                charHealth = value;
                return *this;
            }
    
            Config& maxHealth(int value)
            {
                charMaxHealth = value;
                return *this;
            }
    
            Config& attackPower(int value)
            {
                charAttackPwr = value;
                return *this;
            }
    
            Config& maxAttackPower(int value)
            {
                charMaxAttackPwr = value;
                return *this;
            }
        private:
            string charClass{};
            int charHealth{};
            int charMaxHealth{};
            int charAttackPwr{};
            int charMaxAttackPwr{};
        };
    
        explicit Character(const Config& config) :
            charClass(config.charClass),
            charHealth(config.charHealth),
            charMaxHealth(config.charMaxHealth),
            charAttackPwr(config.charAttackPwr),
            charMaxAttackPwr(config.charMaxAttackPwr) {}
    
        string GetCharClass() { return charClass; }
    
        int GetCharHealth() const { return charHealth; }
        int GetCharAttackPwr() const { return charAttackPwr; }
        int GetCharMaxAttackPwr() const { return charMaxAttackPwr; }
        int GetCharMaxHealth() const { return charMaxHealth; }
    
        void TakeDamage(const int& damageTaken);
        void RestoreHealth(const int& restoreAmount);
        void UpgradeAttackPower(const int& upgradeAmount);
        void DisplayCharacterInfo(Character& character);
    
    private:
        string charClass{};
        int charHealth{};
        int charMaxHealth{};
        int charAttackPwr{};
        int charMaxAttackPwr{};
    };
    
    
    ostream& operator<< (ostream& os, Character& character)
    {
        os << "Class Name: " << character.GetCharClass() << endl;
        os << "Health: " << character.GetCharHealth() << endl;
        os << "Max Health: " << character.GetCharMaxHealth() << endl;
        os << "Attack Power: " << character.GetCharAttackPwr() << endl;
        os << "Max Attack Power: " << character.GetCharMaxAttackPwr() << endl;
    
        return os;
    }
    
    
    void Character::RestoreHealth(const int& restoreAmount)
    {
        if (charHealth + restoreAmount <= charMaxHealth)
        {
            charHealth += restoreAmount;
        }
        if (charHealth + restoreAmount >= charMaxHealth)
        {
            charHealth = charMaxHealth;
        }
    }
    
    
    void Character::TakeDamage(const int& damageTaken)
    {
        if (charHealth > 0)
        {
            charHealth -= damageTaken;
    
            if (charHealth <= 0)
            {
                charHealth = 0;
                cout << "The " << GetCharClass() << ", was defeated." << endl;
            }
        }
    }
    
    
    void Character::DisplayCharacterInfo(Character& character)
    {
        cout << character << endl;
    }
    
    
    int main()
    {
        Character Knight(Character::Config("Knight").health(100).maxHealth(150).attackPower(5).maxAttackPower(5));
        Character Samurai(Character::Config("Samurai").health(50).maxHealth(100).attackPower(7).maxAttackPower(7));
        Character Marine(Character::Config("Marine").health(200).maxHealth(250).attackPower(4).maxAttackPower(4));
    
        //Debug
        cout << "--BEGIN DEBUG--\n" << endl;
    
        Samurai.TakeDamage(Knight.GetCharAttackPwr());
        Samurai.DisplayCharacterInfo(Samurai);
    
        Knight.RestoreHealth(1);
        Knight.DisplayCharacterInfo(Knight);
    
        cout << "--END DEBUG--" << endl;
        //End Debug
    
        return 0;
    }
    Now, the code is not only self-documenting, but also ensures that you don't mix up the order of arguments. What it doesn't do is enforce at compile time that all of the configuration parameters are provided (i.e., you can enforce this at run time, but not really at compile time with this approach).

    EDIT:
    coder777's idea in that other thread does enforce at compile time that all of the configuration parameters are provided because it just names specific types and values while retaining the original constructor, but for ease of use it also requires you to declare all these specific values in advance, rather than at the construction of the object. This could work very well if you only have a limited number of what are effectively subtypes of the Character type (but if so, why not use a simple factory instead?), but it won't work so well if you want more dynamic Character object creation.
    Last edited by laserlight; 11-13-2019 at 04:16 PM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  4. #4
    Registered User
    Join Date
    Jul 2019
    Posts
    34
    Quote Originally Posted by jimblumberg View Post
    Also posted here.
    And? I like having multiple perspectives, I often post on 3 or 4 different programming forums, I often find people on the same forum generally have a certain way of programming than others so the people on each forum provide different answers and solutions.

    @laserlight, Interesting solution, i'll look this over. I thought of using just comments but I thought maybe having actual code document things would be easier and neater. as for the other forums code, I feel like creating namespaces just for documentation is overkill, but maybe i'm wrong, I've only used namespaces a few times and there are gaps in my C++ knowledge.

    I didnt even think about the argument orders being able to be mixed up.

    This could work very well if you only have a limited number of what are effectively subtypes of the Character type (but if so, why not use a simple factory instead?), but it won't work so well if you want more dynamic Character object creation.
    I always like to create my code as if more stuff will always be added in an attempt to future proof it, unless I know for absolute 100% certainty it will not/not abale to be extended further.

    Thanks for the help, I'll play around with the code.

  5. #5
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    Here's what cross-posting means to us.
    How To Ask Questions The Smart Way
    How To Ask Questions The Smart Way

    Cross-posters tend to get ignored when there MO becomes apparent.

    Because it's a waste of OUR time answering questions which often garner the same answers elsewhere.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. How does this purely arithmetic hex char to integer work?
    By XenoReseller in forum C Programming
    Replies: 3
    Last Post: 05-31-2012, 01:13 AM
  2. Replies: 5
    Last Post: 06-30-2011, 03:24 PM
  3. Protected purely virtual function
    By MarkZWEERS in forum C++ Programming
    Replies: 4
    Last Post: 08-07-2009, 01:02 PM
  4. global struct variable best use
    By Kempelen in forum C Programming
    Replies: 2
    Last Post: 06-05-2009, 05:08 AM
  5. Anyone purely self-taught?
    By guitarscn in forum A Brief History of Cprogramming.com
    Replies: 103
    Last Post: 03-15-2008, 04:58 AM

Tags for this Thread