C++, dealing with multiple constructor overloads and redundant code - c++
I've developed a recent interest in (re-)learning programming, so I have taken up C++ as it is a commonly used language. However, I've ran into a roadblock, and I have doubts whether my solution is the best way to go around it.
I have a relatively complex class (for me anyways), with around 20 variables, which have been divided into 4 groups for simplification. It also has a parent class which is called during object initialization.
However, I do not have a need to set these to values other than their default values in all objects, so I have set-up various different constructor overloads to account for all possible combinations (8 constructors in total). Therefore, in order to prevent writing repetitive code, I have written an handful of private functions, only called during the constructor, that set variables to the value I assign when creating a new object.
Is this the optimal way of solving this problem? I have also thought of grouping these variables into classes or structs, but it just feels like that's needlessly complicated, when calling the relevant functions during the various constructor overloads should do the trick. If this is not optimal, what would be the best way of solving this? And why?
I can provide with a more detailed description of my problem, but it'd be a pretty big wall of text (I had first written that one up, but it got way too out of hand). Thank you in advance for your input.
As requested, here is the class definition (Weapon). The parent class (Item) is already defined and working as intended, so I will not paste it, so people will not have to read a massive wall of text.
Weapon class definition:
class Weapon: public Item {
public:
// Default constructor
Weapon();
// Full constructor
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short CooldownType, double CooldownDuration, unsigned short CooldownShot, double CooldownPeriod, unsigned short ReloadType, unsigned short ReloadStyle, double ReloadTime, unsigned short MaxMagazine, unsigned short MaxAmmunition, unsigned short StartEnergy, unsigned short MaxEnergy);
// Constructor for Weapons without Cooldown System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short ReloadType, unsigned short ReloadStyle, double ReloadTime, unsigned short MaxMagazine, unsigned short MaxAmmunition, unsigned short CurrentEnergy, unsigned short MaxEnergy);
// Constructor for Weapons without Reload System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short CooldownType, double CooldownDuration, unsigned short CooldownShot, double CooldownPeriod, unsigned short MaxMagazine, unsigned short MaxAmmunition, unsigned short CurrentEnergy, unsigned short MaxEnergy);
// Constuctor for Weapons without Energy System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short CooldownType, double CooldownDuration, unsigned short CooldownShot, double CooldownPeriod, unsigned short ReloadType, unsigned short ReloadStyle, double ReloadTime, unsigned short MaxMagazine, unsigned short MaxAmmunition);
// Constructor for Weapons without Cooldown nor Reload System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short MaxMagazine, unsigned short MaxAmmunition, unsigned short CurrentEnergy, unsigned short MaxEnergy);
// Constructor for Weapons without Cooldown nor Energy System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short ReloadType, unsigned short ReloadStyle, double ReloadTime, unsigned short MaxMagazine, unsigned short MaxAmmunition);
// Constructor for Weapons without Reload nor Energy System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short CooldownType, double CooldownDuration, unsigned short CooldownShot, double CooldownPeriod, unsigned short MaxMagazine, unsigned short MaxAmmunition);
// Constructor for Weapons without Cooldown, Reload nor Energy System
Weapon(unsigned GenericID, bool NameFlag, double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short maxMagazine, unsigned short MaxAmmunition);
~Weapon();
void m_print();
/*Edited public get and set functions for each variable as they are not relevant*/
private:
// Ubiquitous variables
unsigned short WepGenericID = 0;
unsigned short WepVariantID = 0;
unsigned short WepSkinID = 0;
double EquipLoad = 0;
double EquipLoadperAmmo = 0;
unsigned short ModesNo = 1;
Mode* pModes = NULL;
unsigned short MaxAmmunition = 0;
unsigned short CurrentAmmunition = 0;
unsigned short MaxMagazine = 0;
unsigned short CurrentMagazine = 0;
// Cooldown System variables
bool WeaponCooldown = false;
unsigned short CooldownType = 0;
double CooldownDuration = 0;
unsigned short CooldownAction = 0;
double CooldownPeriod = 0;
// Reload System variables
unsigned short ReloadType = 0;
unsigned short ReloadStyle = 0;
double ReloadTime = 0;
// Energy System variables
unsigned short CurrentEnergy = 0;
unsigned short MaxEnergy = 0;
//Constructor Auxiliary Functions
void m_setGeneralWeapon(double EquipLoad, double EquipLoadperAmmo, unsigned short ModesNo, Mode* pModes, unsigned short MaxMagazine, unsigned short MaxAmmunition);
void m_setCooldownSystem(unsigned short CooldownType, double CooldownDuration, unsigned short CooldownAction, double CooldownPeriod);
void m_setReloadSystem(unsigned short ReloadType, unsigned short ReloadStyle, double ReloadTime);
void m_setEnergySystem(unsigned short StartEnergy, unsigned short MaxEnergy);
void m_setWeaponIDs();
void m_WepNameDecisionTree();
string m_searchName();
};
Item parent class definition
class Item {
public:
Item();
Item(unsigned GenericID);
Item(unsigned GenericID, bool NameFlag);
~Item();
void m_setCustomName();
private:
unsigned GenericID = 0;
unsigned short GenCategoryID = 0;
unsigned short GenSubCategoryID = 0;
bool NameFlag = false;
string ItemName = "Missingno";
unsigned long InstanceID = 0;
};
make seperate classes for your subsystems.
create your weapons using a builder/factory pattern:
How to implement the factory method pattern in C++ correctly
https://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Design_Patterns/Creational_Patterns
you could also seperate the ammo, leaving you with only few actual members. This way you can build everything with a more modular approach, giving you the possibility to easier extend or modify your functionality
One problem I see with the Weapon API as posted is that the constructors take a lot of loosely-typed arguments, which makes the code that uses them hard to understand and verify. For example, say you (or your fellow developer) have used your constructor API to add this line to your codebase:
Weapon bfg(id, true, 3.0, 5.0, 6, modesPtr, COOLDOWN_QUICK, 5.0, 3, 4, RELOAD_SLOW, RELOAD_ANYTIME, 3.8, 12, 14);
Reading that line, it's very hard to see what most of the numbers mean. If an argument was accidentally omitted, or the ordering of two of the arguments was reversed, it will likely not be obvious (either you or to the compiler) that there is a bug there; instead, you might not find out about the error until some play tester (or customer?) files a bug report, which is an expensive and time-consuming way to discover errors.
Therefore, I recommend reducing the number of arguments you have to pass to the constructor to as few as you can get away with. For example, another way to write the above might be this:
Weapon bfg(id, true);
bfg.SetEquipLoad(3.0);
bfg.SetEquipLoadPerAmmo(5.0);
bfg.SetModes(6, modesPtr);
bfg.SetCooldownType(COOLDOWN_QUICK);
[...]
Admittedly that's a lot more verbose (and it does make it possible to forget to set something you should have set), but at least when you look at it, it's very obvious that 3.0 applies to the EquipLoad setting and 5.0 applies to the EquipLoadPerAmmo setting, and not the other way around. i.e. you don't have to constantly look back and forth between your .h file and the code to try and figure out what each value is referring to.
Note that each set-method should take all of the parameters needed to yield a useful result; so for example if it doesn't make any sense to specify an EquipLoad without also specifying EquipLoadPerAmmo, then you might as well have both of those set by a single call instead:
bfg.SetEquipLoadAndEquipLoadPerAmmo(3.0, 5.0);
... so that it becomes impossible (i.e. a compile-time error) for a coder to make the mistake of setting one but neglecting to set the other.
As for handling the verboseness to minimize redundant code, the next step would be to wrap the above code inside a function, so that for any given weapon-type there is only one place that creates it, e.g. :
Weapon MakeBFG(unsigned id)
{
Weapon bfg(id, true);
bfg.SetEquipLoad(3.0);
bfg.SetEquipLoadPerAmmo(5.0);
bfg.SetModes(6, modesPtr);
bfg.SetCooldownType(COOLDOWN_QUICK);
[...]
return bfg;
}
Now the rest of your code can just call Weapon bfg = MakeBFG(idCounter++); whenever it wants to create a new BFG gun.
Outside of that, I agree with the other poster -- your class seems to be handling a number of different things, and if you can find a way to decompose it into multiple smaller classes (not all of which need to be exposed via the public API; private classes are great), that would probably help you manage the overall complexity of the code; doing so is especially beneficial if you think you will want to continue to add new features/behaviors in the future, since if you keep everything together in a single class, the complexity of that class will quickly get out of hand as you try to make it support more and more different behaviors/use-cases.
Related
Store a long double in several double variables
I have to store a long double variable in JSON format. JSON supports double variables but not long double. Then I have to split the long double value in several double values, but I don't know how to do that easily. I already did that for long long int variables by this way : std::vector<long int> long_long_int__to__long_int(long long int x) { std::vector<long int> split; const unsigned long int n = sizeof(long long int) / sizeof(long int); for(unsigned long int i = 1; i < n; ++i) { split.push_back((long int)(x % (long long int)std::numeric_limits<long int>::max)); x /= (long long int)std::numeric_limits<long int>::max; } split.push_back(x); return split; } Any idea how to do that with floating points variables ?
Here is the solution I can think of, and it's applicable to your long long int problem as well. consider any type you want to store as a array of hex values, whether it's a long long int or long double. choose one Binary-to-text encoding algorithm and use it to convert the array to a readable string. store the string to JSON
Store two doubles: (double)x and (double)(x - (double)x). (And pay attention to compilers bugs and switches affecting the handling of precision, they may cause issues when using such techniques, a gcc bug for instance).
Too many types in declaration C++
When unsigned/signed long int a; is possible why unsigned/signed long float/double a; is not possible ? Why do I get too many types in declaration error for the latter and not for the former ?
There are three floating point types: float, double and long double. None of these have unsigned equivalents, so putting signed or unsigned in front of them is not valid. There is no such type as long float.
You are getting that message because a long double exists, but an unsigned long double does not. unsigned can also be interpreted as an int, therefore you possess two types in the latter declaration: unsigned and long double. I do not believe there is a long float in C++.
That is because the first (long int) is a documented variable type, while the second isn't. The data types that the C++ language supports are: char unsigned char signed char int unsigned int signed int short int unsigned short int signed short int long int signed long int unsigned long int float double long double
Limiting structures size by use of :
Why this piece of code is needed ? typedef struct corr_id_{ unsigned int size:8; unsigned int valueType:8; unsigned int classId:8; unsigned int reserved:8; } CorrId; I did some investigation around it and found that this way we are limiting the memory consumption to just what we need. For E.g. typedef struct corr_id_new{ unsigned int size; unsigned int valueType; unsigned int classId; unsigned int reserved; } CorrId_NEW; typedef struct corr_id_{ unsigned int size:8; unsigned int valueType:8; unsigned int classId:8; unsigned int reserved:8; } CorrId; int main(){ CorrId_NEW Obj1; CorrId Obj2; std::cout<<sizeof(Obj1)<<endl; std::cout<<sizeof(Obj2)<<endl; } Output:- 16 4 I want to understand the real use case of such scenarios? why can't we declare the struct something like this, typedef struct corr_id_new{ unsigned _int8 size; unsigned _int8 valueType; unsigned _int8 classId; unsigned _int8 reserved; } CorrId_NEW; Does this has something to do with compiler optimizations? Or, what are the benefits of declaring the structure that way?
I want to understand the real use case of such scenarios? For example, structure of status register of some CPU may look like this: In order to represent it via structure, you could use bitfield: struct CSR { unsigned N: 1; unsigned Z: 1; unsigned C: 1; unsigned V: 1; unsigned : 20; unsigned I: 1; unsigned : 2; unsigned M: 5; }; You can see here that fields are not multiplies of 8, so you can't use int8_t, or something similar.
Lets see a simple scenario, typedef struct student{ unsigned int age:8; // max 8-bits is enough to store a students's age 255 years unsigned int roll_no:16; //max roll_no can be 2^16, which long enough unsigned int classId:4; //class ID can be 4-bits long (0-15), as per need. unsigned int reserved:4; // reserved }; Above case all work is done in 32-bits only. But if you use just a integer it would have taken 4*32 bits. If we take age as 32-bit integer, It can store in range of 0 to 2^32. But don't forget a normal person's age is just max 100 or 140 or 150 (even somebody studying in this age also), which needs max 8-bits to store, So why to waste remaining 24-bits.
You are right, the last structure definition with unsigned _int8 is almost equivalent to the definition using :8. Almost, because byte order can make a difference here, so you might find that the memory layout is reversed in the two cases. The main purpose of the :8 notation is to allow the use of fractional bytes, as in struct foo { uint32_t a:1; uint32_t b:2; uint32_t c:3; uint32_t d:4; uint32_t e:5; uint32_t f:6; uint32_t g:7; uint32_t h:4; } To minimize padding, I strongly suggest to learn the padding rules yourself, they are not hard to grasp. If you do, you can know that your version with unsigned _int8 does not add any padding. Or, if you don't feel like learning those rules, just use __attribute__((__packed__)) on your struct, but that may introduce a severe performance penalty.
It's often used with pragma pack to create bitfields with labels, e.g.: #pragma pack(0) struct eg { unsigned int one : 4; unsigned int two : 8; unsigned int three : 16 }; Can be cast for whatever purpose to an int32_t, and vice versa. This might be useful when reading serialized data that follows a (language agnostic) protocol -- you extract an int and cast it to a struct eg to match the fields and field sizes defined in the protocol. You could also skip the conversion and just read an int sized chunk into such a struct, point being that the bitfield sizes match the protocol field sizes. This is extremely common in network programming -- if you want to send a packet following the protocol, you just populate your struct, serialize, and transmit. Note that pragma pack is not standard C but it is recognized by various common compilers. Without pragma pack, however, the compiler is free to place padding between fields, reducing the use value for the purposes described above.
Array of pointers like (*(volatile unsigned long *)0x40004000)
I am having a very hard time figuring out how to solve the following problem. I am on an embedded system with very little memory and want to minimize memory usage. Pointers have always confused the heck out of me and will always do. I have a whole bunch of defines for register addresses: #define GPIO_PORTA_BASE (*((volatile unsigned long *)0x40004000)) #define GPIO_PORTB_BASE (*((volatile unsigned long *)0x40005000)) //etc.. These registers are direct accessible. e.g: GPIO_PORT_BASE &= 0x01; What I need is an array that contains the above registers so that I can easily map them to an index. e.g: not_sure_what_to_declare_the array_as port_base_array[] { GPIO_PORTA_BASE, GPIO_PORTB_BASE, //etc } What I need to end up being able to do is something like this: volatile unsigned long *reg; *reg_a = port_base_array[0]; reg_a &=0x1; I am using gcc to compile my code for arm cortex m3. Any insight would be appreciated.
I don't know why #Etienne deleted his answer, but it contained the essential information: The address is cast to volatile unsigned long *. That's what you need an array of. typedef volatile unsigned long* reg_addr; reg_addr registers[] = { &GPIO_PORTA_BASE, &GPIO_PORTB_BASE, // ... }; We need to take the address again (&GPIO_PORTA_BASE), since the macro automatically dereferences them. Access as: *registers[i] &= your_value;
Usual way is to declare a struct, for example : struct RegsAtAddrA { unsigned int array1[10]; char val1; // etc }; then to access it : volatile RegsAtAddrA *pRegsA = (volatile RegsAtAddrA *) 0x40004000; pRegsA->val1= 'a'; //etc EDIT: I just realized that I haven't answered the question. So, here it is : #include <iostream> unsigned long a=1; unsigned long b=2; volatile unsigned long *port_base_array[] = { &a, &b, //etc }; int main() { std::cout<<"a="<<*port_base_array[0]<<std::endl; std::cout<<"b="<<*port_base_array[1]<<std::endl; }
What I think you're trying to do is something like this: volatile unsigned long * gpio_porta = &GPIO_PORTA_BASE; If you're using C++, you could also do the following: volatile unsigned long & reg_foo = (&GPIO_PORTA_BASE)[3]; volatile unsigned long & reg_foo = gpio_porta[3]; And use it as: reg_foo &= 0x1; However, most times I would expect a base address register to actually be stored as a pointer, rather than as the dereference of the pointer. Because of that, I would probably want your macros to be defined as: #define GPIO_PORTA_BASE ((volatile unsigned long *) 0x40004000) #define GPIO_PORTB_BASE ((volatile unsigned long *) 0x40005000) And then you could simply access them as GPIO_PORTA_BASE[3] &= 0x1
If I'm getting you right, this should be enough: volatile unsigned long* GPIO_PORTA = (volatile unsigned long*) 0x40004000; You could use that as volatile unsigned long regxx = GPIO_PORTA[0x17]; // even GPIO_PORTA[10] &= 0xF000;
C++ Integer overflow problem when casting from double to unsigned int
I need to convert time from one format to another in C++ and it must be cross-platform compatible. I have created a structure as my time container. The structure fields must also be unsigned int as specified by legacy code. struct time{ unsigned int timeInteger; unsigned int timeFraction; } time1, time2; Mathematically the conversion is as follows: time2.timeInteger = time1.timeInteger + 2208988800 time2.timeFraction = (time1.timeFraction * 20e-6) * 2e32 Here is my original code in C++ however when I attempt to write to a binary file, the converted time does not match with the truth data. I think this problem is due to a type casting mistake? This code will compile in VS2008 and will execute. void convertTime(){ time2.timeInteger = unsigned int(time1.timeInteger + 2209032000); time2.timeFraction = unsigned int(double(time1.timeFraction) * double(20e-6)*double(pow(double(2),32))); }
Just a guess, but are you assuming that 2e32 == 2^32? This assumption would make sense if you're trying to scale the result into a 32 bit integer. In fact 2e32 == 2 * 10^32
Slightly unrelated, I think you should rethink your type design. You are basically talking about two different types here. They happen to store the same data, albeit in different results. To minimize errors in their usage, you should define them as two completely distinct types that have a well-defined conversion between them. Consider for example: struct old_time { unsigned int timeInteger; unsigned int timeFraction; }; struct new_time { public: new_time(unsigned int ti, unsigned int tf) : timeInteger(ti), timeFraction(tf) { } new_time(new_time const& other) : timeInteger(other.timeInteger), timeFraction(other.timeFraction) { } new_time(old_time const& other) : timeInteger(other.timeInteger + 2209032000U), timeFraction(other.timeFraction * conversion_factor) { } operator old_time() const { old_time other; other.timeInteger = timeInteger - 2209032000U; other.timeFraction = timeFraction / conversion_factor; return other; } private: unsigned int timeInteger; unsigned int timeFraction; }; (EDIT: of course this code doesn’t work for the reasons pointed out below. Now this code can be used frictionless in a safe way: time_old told; /* initialize … */ time_new tnew = told; // converts old to new format time_old back = tnew; // … and back.
The problem is that (20 ^ -6) * (2 e32) is far bigger than UINT_MAX. Maybe you meant 2 to the power of 32, or UINT_MAX, rather than 2e32. In addition, your first line with the integer, the initial value must be less than (2^32 - 2209032000), and depending on what this is measured in, it could wrap round too. In my opinion, set the first value to be a long long (normally 64bits) and change 2e32. If you can't change the type, then it may become necessary to store the field as it's result in a double, say, and then cast to unsigned int before use.