2

I am attempting to create an array of objects within another object (specifically an array of Check within Checkbook. I am not allowed to use a vector in place of a dynamic array to store the objects, which is maddening, but those are the rules that have been stipulated to me.

The issue i'm struggling with is that I need to provide several variables to the check constructor because it needs to construct an Money object within the check object. So i'm receiving an error claiming that Check* checkArray = new Check[]; has no default constructor.

I have since added a default constructor which is just Check::Check() {};, but how can I dynamically fill an array without arguments being initially passed to the constructor upon creation? I am very new to OOP and am struggling to manage classes within classes. Note: The money Class was predefined and implemented

The data for these relevants checks are stored in a .txt file in the form of int(check number) '\t' double(check amount) '\t' int(bool for cashed represented in 0 or 1) and I am temporarily storing the data in a struct DataTransferStruct and then storing the structs in a vector just to test, but I can't use the vector in my final implementation. Am I approaching this in a bad way?

Relevant Code Below:

class Money
{
    private:
        long all_cents;

    public:
        friend Money operator +(const Money& amount1, const Money& amount2); //Returns sum of the values of amount1 and amount2
        friend Money operator -(const Money& amount1, const Money& amount2); //Returns amount1 minus amount2
        friend Money operator -(const Money& amount); //Returns the negative of the value of amount
        friend bool operator ==(const Money& amount1, const Money& amount2); //Returns true if amount1 and amount2 have the same value; false otherwise
        friend bool operator <(const Money& amount1, const Money& amount2) { return (amount1.all_cents < amount2.all_cents); }; //Returns true if amount1 is less than amount2; false otherwise

        Money(long dollars, int cents); // Initializes the object so its value represents an amount with the dollars and cents given by arguments.  If the amount is 
        //negative, then both dollars and cents should be negative

        Money::Money(long dollars) : all_cents(dollars * 100) {} //Initializes the object so its value represents $dollars.00

        Money::Money() : all_cents(0) {}//Initializes the object so its value represnets $0.00

        double get_value() const; //Returns the amount of money recorded in the data portion of hte calling object
};

Check Class

class Check
{
    //Check dataTypes
    private:
        int checkNum;
        Money checkAmount;
        bool cashed;

    public:
        //Constructor
        Check::Check(long dollar_Value, int cents_Value, int check_Num, int cashed_) : checkAmount(CreateMoneyClass(dollar_Value, cents_Value)) { checkNum = check_Num; if (cashed_ == 1)cashed = true; else cashed = false; };
        Check::Check() {};
        //Deconstructor
        Check::~Check() {};

        //Member functions
        Money CreateMoneyClass(long dollar_Value, int cents_Value);
        int GetCheckNum() const { return checkNum; };
        double GetCheckAmount() const { return checkAmount.get_value(); };
        bool CheckCashed() const { return cashed; };

};

Money Check::CreateMoneyClass(long dollar_Value, int cents_Value)
{
    //Function that creates a Money object and returns to checkAmount within Check class 
    Money temp(dollar_Value,cents_Value);

    return temp;
}

Just started CheckBook class

class CheckBook
{
    //Checkbook contains an array of checks
    private:
        Check* checkArray = new Check[];

};

Method I was using to store information

NewFile_Open(newFile);

    //take in and format each data line w/ struct and then construct check in dynamic growing array
    while (newFile >> temp.checkID)
    {
        newFile >> temp.rawMoneyAmount;
        newFile >> temp.checkCashed;

        //set dollars = rawMoneyAmount which drops cents
        temp.dollars = temp.rawMoneyAmount;

        //Get cents as int
        temp.cents = (int)((temp.rawMoneyAmount - temp.dollars) * 100);
    }
5
  • 3
    Pick a different employer with less silly "rules"? Commented Mar 28, 2017 at 14:36
  • @KerrekSB lol nice Commented Mar 28, 2017 at 14:40
  • Well, you have to reimplement std::vector yourself then. You'll need to allocate appropriately sized and aligned raw storage, then use placement-new to construct your objects within as needed. Commented Mar 28, 2017 at 14:43
  • @Quentin I am not allowed to use a vector. I know how to create a dynamic array and resize it accordingly, but I am not sure how to do that within a class structure. Commented Mar 28, 2017 at 14:46
  • 1
    @StormsEdge yes, that's what "reimplementing std::vector" means: creating your own version of it. Commented Mar 28, 2017 at 14:49

4 Answers 4

2

You should not make an array like this in class

class CheckBook
{
    //Checkbook contains an array of checks
    private:
        Check* checkArray = new Check[];

};

You should define it in class CheckBook constructor.

Like

class CheckBook
    {
        //Checkbook contains an array of checks
        private:
            Check* checkArray;
        Checkbook()
        {
         checkArray = new checkArray[size];
        }
    };
Sign up to request clarification or add additional context in comments.

Comments

2

The correct way to build an array of objects having no default constructor is to use the placement new syntax to separate the allocation of the array and the construction of the objects.

Checkbook::CheckBook(int size) { // A Checkbook containing size Checks
    // first the allocation
    Check * checkArray = reinterpret_cast<Check *>(new char[size * sizeof(Check)]);
    // then the actual construction
    for (int i=0; i<size; i++) {
        int checkNum = ...; // compute a check number possibly depending on i
        //actual construction of checkArray[i]
        new(checkArray + i) Check(0., 0., checkNumber, false);
    }
}

6 Comments

What does using char as the basis of allocation do to alignment? Couldn't that cause the structure to be one-byte-aligned thus losing performance? Casting some empty space to the type is a nice trick however, avoid any sort of constructor even. However, bypassing the constructor system for objects altogether sounds like you'd need to be careful there as it's bypassing checks and balances for type safety.
@JasonLang: char (or byte) is the memory unit here: all objects (except for bit fields) are required to occupy an integer number of bytes. Allocated memory is guaranteed to be properly allocated for any object type. And the constructor system is not bypassed since it is triggered (later) by the placement new. What is true is that trying to use checkArray between its allocation and the construction of its objects do invoke Undefined Behaviour.
Well if you're casting to a larger item it would be nice to force the alignment so that it is optimized for that
@JasonLang: C++ allocation sytem (as well as C malloc) is required by standard to return an allocated memory that is suitably aligned for any object type. That's the reason why it is allowed to overwrite allocated memory by an object of a different type, and that is done under the hood by normal new or new[] allocation.
yes but you're allocating as char, then reinterpreting the pointer. It won't necessarily be properly aligned for the reinterpreted type. Sure it will work but not necessarily as efficiently.
|
1

One issue is that you are trying to initialize the checkArray object in the class definition. This is not the proper place to do so. Instead, the checkArray type should only be declared here, and initialized in the constructor of CheckBook:

class CheckBook
{
    public:
        CheckBook()
         : checkArray(new Check[]) // still an error here!
        {}
    private:
        Check* checkArray;

};

This is still not correct yet, however, as C arrays are fixed size, which is a big problem: you would need to know the size ahead of time and pass this to the constructor:

class CheckBook
{
    public:
        CheckBook(unsigned int numChecks)
         : checkArray(new Check[numChecks])
        {}
    private:
        Check* checkArray;

};

You should really push back on the short-sighted requirement that std::vector is not allowed, unless this is a homework problem. In that case, an array is likely not the correct container to use. (I won't say what would be better on the chance that this is homework).

Comments

0

I'd suggest make an single Init method, and have all constructors and initialization pass through that. That way if you later need a default constructor for CheckBooks (e.g. you need an array of CheckBooks) then it's easier to convert it.

class CheckBook
{
public:
    CheckBook(unsigned int numChecks) : checkArray(0), size(0)
    {
           Init(numChecks);
    }

    void Init(int _size)
    {
          if(checkArray != 0) // if there's already a checkArray, replace it.
          {
                delete [] checkArray;
          }

          size = _size;
          checkArray = new Check[size];

          // for completeness you'd want to check that checkArray is not null 
          // otherwise the memory allocation failed

          for(int i = 0; i < size; ++i)
          {
               // do your per-check set-ups here
          }
    }

    ~CheckBook() // make sure to clean up the dynamic memory
    {
          delete [] checkArray;
    }
private:
    Check* checkArray;
    int size;
};

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.