Question

Suppose you've got a file that contains the following:

  • A file header containing: a) the first empty record number, b) the size of each record, and c) the maximum number of records that the file can hold.
  • Records containing: a) a byte that indicates whether the record is free or occupied, b) a fixed number of fields.

When I want to read a random record, I use this function:

int FixedLengthRecordFile :: read (int numRec, FixedLengthFieldsRecord & rec) 

The problem is that, in order for this function to work, I must create a FixedLengthFieldsRecord beforehand, and this implies specifying the number of fields that this record has... which is not what I want. I want the file handler to be "smart" enough to recognize how many fields the record has, and to create a FixedLengthFieldsRecord on-the-fly as it reads it.

How can I accomplish this? The function has to return an int because I use it as an exit code (error/success).

Was it helpful?

Solution

Potentially, you could define the FixedLengthFieldsRecord class so that the class itself enforces the correct record length, but allows for an arbitrary number of fields in each record. A possible good way to accomplish this would be to have the CONSTRUCTOR read in the next record from the file.

The class (minus some necessary error-checking, which you could add) could be written in similar fashion to the following (this class assumes that you read the first line of the file BEFORE creating objects of this class):

using namespace std;
class FixedLengthFieldsRecord
{

    public:

        FixedLengthFieldsRecord(int const recordLength, istream & s); // Set the length of the record in the constructor
        bool IsEmpty() const;
        int FieldCount() const; // variable number of fields allowed; but LENGTH of record is enforced (see below)

        bool IsValidRecord(); // Does the record contain the correct number of bytes?
        string GetField(int const index) const; // This could throw an exception if the record is not valid

    protected:
        // Could have sophisticated functions here to replace fields, remove fields, reorder fields, etc.

    // This section contains the actual implementation.
    private:
        vector<string> fields; // The vector contains a VARIABLE number of fields
        bool is_empty;
        bool is_valid;
        int const record_length; // This contains the LENGTH of the record; it is set in the constructor and cannot be changed

    // The following variable and function store (and access) ALL the records
    static vector<FixedLengthFieldsRecord> records;
    static read(int numRec, FixedLengthFieldsRecord & rec);

}

FixedLengthFieldsRecord::FixedLengthFieldsRecord(int const recordLength_, istream & s)
    : record_length(recordLength)
{
    // pseudocode provided here
    // this functionality could be factored into other functions that are called by the constructor

    is_valid = true;

    is_empty = ReadFirstByte(s); // ReadFirstByte (not shown) reads first byte of current line and returns true if it indicates an empty record

    if (!is_empty)
    {
        string field;
        int currentRecordLength = 0;
        while (ReadNextField(s, field)) // ReadNextField() returns true if another field was read from the line (i.e., not end-of-line
        {
            currentRecordLength+= field.length();
        }
        if (currentRecordLength != record_length)
        {
            is_valid = false;
        }
        if (currentRecordLength > record_length)
        {
            break;
        }
        if (is_valid)
        {
            fields.push_back(field);
        }
    }
    if (is_valid)
    {
        records.push_back(*this); // not efficient, nor thread safe - deep copy occurs here
    }
}

bool FixedLengthFieldsRecord::IsEmpty()
{
    return is_empty();
}

bool FixedLengthFieldsRecord::IsValidRecord()
{
    return is_valid;
}

string FixedLengthFieldsRecord::GetField(int const index)
{
    if (!is_valid)
    {
        // throw an exception, or return an empty string
    }
    if (index < 0 || index >= fields.size())
    {
        // throw an exception, or return an empty string
    }
    return fields[index];
}

FixedLengthFieldsRecord::read(int numRec, FixedLengthFieldsRecord & rec)
{
    if (numRec < 0 || numRec >= records.size())
    {
        // throw an exception, or just return
    }
    rec = records[numRec];
}

OTHER TIPS

The FixedLengthRecordFile should open the file (or better, take an std::istream in the constructor), read the header and then the FixedLengthRecordFile::read(...) member function can use std::istream::seekg and std::istream::read to get the data.

Now, to your actual problem. Why do you want the FixedLengthRecordFile::read function to return an int while it takes the record by reference? Wouldn't a signature like

FixedLengthFieldsRecord FixedLengthRecordFile::read(size_t numRec, int& foo);

be easier?

If you insist on your original signature, make the FixedLengthFieldsRecord default constructor initialize the object to an invalid state (e.g. add a bool isValid flag that is set to false), and add a setData(size_t length, const char* data) member function which can then be called by the FixedLengthRecordFile::read function.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top