Использование указателя на класс объектов в качестве ключа в примере с несколькими картами

Я много читал, но я не могу понять, как это должно работать. Эта программа изначально использовала член multimap<CFile, Filetype>, но мне нужно переделать его как multimap<CFile*, Filetype>. Из моего небольшого понимания указателей мне нужно сделать мультикарту, в которой в качестве ключа есть указатели на CFile объекты, но я не могу это реализовать. На самом деле я помещаю указатели внутри мультикарты в конструкторе CDirectory, но когда я пытаюсь напечатать объект CDirectory, программа вылетает, так что одна (из многих ошибок) — моя перегрузка ostream operator<< для класса CDirectory. Я действительно не знаю, с чего даже начать, я понимаю базовую логику указателя, но, похоже, я не могу ее реализовать.


class CFile {
    string m_strFile;
    unsigned int m_size;
public:
   /*constructors, get() and set() functions are
     implemented but I've deleted them for the example */

    bool operator< (const CFile& obj) const {
        return (m_strFile < obj.m_strFile);
    }
    bool operator== (const CFile& obj) const {
        return (m_size == obj.m_size);
    }
    friend ostream& operator<< (ostream& ost, const CFile& obj) {
        return ost << "name: " << obj.m_strFile << ", size: " << obj.m_size;
    }
    friend istream& operator>> (istream& ist, CFile& obj) {
        return ist >> obj.m_strFile >> obj.m_size;
    }
};

class CDirectory {
    string m_strDirectory;
    enum class Filetype {
        Archive, Hidden, ReadOnly, System, FileNotSupported
    };
    multimap <CFile*, Filetype> m_DirectoryMap;
public:
    /* overloading operator<< for class CDirectory and uses the friend function filetypeToString to convert the enum value to string */
    friend std::ostream& operator<<(std::ostream &os, const CDirectory &dir) {
        os << dir.m_strDirectory << "\n";
        auto p = m_DirectoryMap.begin();
        while ( p != m_DirectoryMap.end()) {
           os << p->first->getFileName() << '\t' << p->first->getFileSize() << '\t' <<  CDirectory::filetypeToString(p->second) << '\n';
           ++p;
        }
        return os;
    }
    /* comparator function, used to find the min and max files by size
       - takes 2 pairs of the multimap and compares their CFile objects filesize */
    static bool Greater(const pair<const CFile, Filetype>& a,
                    const pair<const CFile, Filetype>& b) {
    return (a.first.getFileSize() < b.first.getFileSize());
    }
    /* explicit constructor - reads data from a file and inserts pairs
      of types pair <CFile, enum Filetype> in a multimap */
    CDirectory (const string& n) {
        fp.open (n, ios::in);
        if (!fp) {
            throw std::runtime_error("Could not open file");
        }
        string dirName, fileName,  fType;
        int fileSize;
        Filetype filetype;
        fp >> dirName;
        m_strDirectory = dirName;
        while (fp >> fileName >> fileSize >> fType) {
            CFile obj (fileName, fileSize);
            CFile* ptr = &obj;
            if (fType == "Archive")
                filetype = Filetype::Archive;
            else if (fType == "Hidden")
                filetype = Filetype::Hidden;
            else if (fType == "ReadOnly")
                filetype = Filetype::ReadOnly;
            else if (fType == "System")
                filetype = Filetype::System;
            else
                filetype = Filetype::FileNotSupported;
            m_DirectoryMap.insert(pair<CFile*, Filetype>(ptr, Filetype(filetype)));
        }
    }
    string getDirectory () const { return m_strDirectory; }
    void printMap () {
         auto p = m_DirectoryMap.begin();
         cout << m_strDirectory << endl;
         while ( p != m_DirectoryMap.end()) {
                cout << endl << p->first->getFileName() << '\t' << p->first->getFileSize() << '\t' << filetypeToString(p->second) << endl;
                ++p;
        }
    }
    int countDuplicates( const string& strToCount ) const {
        CFile obj (strToCount, 0);
        CFile* ptr = &obj;
        int numberOfDuplicates = m_DirectoryMap.count(ptr);

        if (numberOfDuplicates > 1)
            return numberOfDuplicates;
        else if (numberOfDuplicates == 1)
            return 1;
        else
            return 0;
    }
     void removeDuplicates( const string& strToRemove ) {
        CFile obj (strToRemove, 0);
        CFile* ptr = &obj;
        pair <multimap<CFile*,Filetype>::iterator, multimap<CFile*,Filetype>::iterator> range;
        range = m_DirectoryMap.equal_range(ptr);
        auto it = range.first;
        ++it;
        while (it != range.second)
            it = m_DirectoryMap.erase(it);
    }
 /*   CFile findMaxSize() const {
        multimap<CFile*, Filetype>::const_iterator result;
         result = std::max_element(m_DirectoryMap.begin(), m_DirectoryMap.end(), Greater);
         CFile obj(result.first->getFileName(), result.first->getFileSize());
         return obj;
    }
    CFile findMinSize() const {
        multimap<CFile*, Filetype>::const_iterator result;
        result = std::min_element(m_DirectoryMap.begin(), m_DirectoryMap.end(), Greater);
        CFile obj(result.first->getFileName(), result.first->getFileSize());
        return obj;
    } */
    static std::string filetypeToString(Filetype type) {
        switch (type) {
            case Filetype::Archive:
                return "archive";
                break;
            case Filetype::Hidden:
                return "hidden";
                break;
            case Filetype::ReadOnly:
                return "read-only";
                break;
            case Filetype::System:
                return "system";
                break;
            case Filetype::FileNotSupported:
                return "not-supported";
                break;
        }
    }
};

int main () {
    /* - Catching if the file exists, prompt the user untill a correct name is given
       - Making an object of type CDirectory, reading data from a file and inserting
       pairs of <CFile, Filetype> in a multimap.
       - Counting the number of duplicated filenames and removing them (leaving only 1).
       - Finding the min and max files by size and printing their objects. */
    string filename = "";
    int numberOfDuplicates = 0;
    cout << "Please enter input file name: \n";
    string iname = "";
    bool done = false;
    CDirectory obj();
    while (!done && cin >> iname) {
        ifstream ist{iname};
        try {
            CDirectory obj(iname);
            done = true;

            cout << "The original multimap (ordered by filename) contains the following data: \n\n";
            system("pause");
            cout << obj;

            cout << "\n\nCheck if the file has any duplicates. Enter a filename:\n\n";
            do  {
                cin >> filename;
                numberOfDuplicates = obj.countDuplicates(filename);
                if ( numberOfDuplicates > 1) {
                    cout << "The file has " << numberOfDuplicates << " duplicates.";
                    cout << "Removing duplicates of " << filename << ". \n\n";
                }
                else if (numberOfDuplicates == 1)
                    cout << "The file " << filename << " does not have any duplicates.\n\n";
                else if (numberOfDuplicates == 0)
                    cout << "The file " << filename << " is not in the multimap. Please enter a new filename:\n\n";
            } while (!(numberOfDuplicates > 0));

            system("pause");
            obj.removeDuplicates(filename);
            cout << "The updated multimap (ordered by filename) contains the following data: \n\n";
            cout << obj;

        }  catch (std::exception &ex) {
            std::cout << ex.what() << "!\n" << "Please try again.\n";
            }
    }
    getch();
    return 0;
}

person Calihog    schedule 21.03.2016    source источник


Ответы (1)


In

while (fp >> fileName >> fileSize >> fType) {
    CFile obj (fileName, fileSize);
    CFile* ptr = &obj;
    if (fType == "Archive")
        filetype = Filetype::Archive;
    else if (fType == "Hidden")
        filetype = Filetype::Hidden;
    else if (fType == "ReadOnly")
        filetype = Filetype::ReadOnly;
    else if (fType == "System")
        filetype = Filetype::System;
    else
        filetype = Filetype::FileNotSupported;
    m_DirectoryMap.insert(pair<CFile*, Filetype>(ptr, Filetype(filetype)));
}

Вы создаете локальную переменную цикла obj с

CFile obj (fileName, fileSize);

Затем вы сохраняете указатель на этот объект с помощью

CFile* ptr = &obj;
//...
m_DirectoryMap.insert(pair<CFile*, Filetype>(ptr, Filetype(filetype)));

Затем вы начинаете цикл заново. К сожалению, когда вы достигаете конца цикла, все объекты цикла уничтожаются, а затем они воссоздаются, когда цикл начинается с самого начала. Это означает, что на карте теперь есть указатель на объект, который был уничтожен. Этот указатель больше недействителен, и его использование является поведением undefined.

Быстрым решением было бы создать указатель вместо автоматического объекта, такого как

CFile* obj = new CFile(fileName, fileSize);

И затем вы сохраняете этот указатель на карте. Это означает, что вам нужно очистить память, которую вы выделили, когда закончите с картой. Вам нужно будет перебрать карту и вызвать delete для каждого ключа.

Затем у нас есть еще одна проблема, которая при использовании

multimap <CFile*, Filetype> m_DirectoryMap;

Карта будет сортировать ключи по адресу, который содержит указатель, а не по адресу, указанному на CFile. Чтобы карта сортировалась по фактическим CFile, вам нужно написать собственный компаратор, который будет принимать два CFile* и возвращать сравнение указанных на CFiles.

person NathanOliver    schedule 21.03.2016
comment
Очень редко использование указателей в качестве ключей карты без надлежащего компаратора является хорошей идеей. - person SergeyA; 21.03.2016
comment
@SergeyA Добавлено к ответу об этом. - person NathanOliver; 21.03.2016
comment
@NathanOliver, о... правильно. Я попробовал ваш способ создания указателей и их хранения, а также создал структуру в качестве пользовательского компаратора, так что, похоже, это отлично работает для меня. У меня появилось еще 2 вопроса - где именно в моей программе я должен перебирать карту и удалять все указатели, в конце main? А в функциях для расчета мин и макс получаю ошибку 'std::multimap<CFile*, CDirectory::Filetype>::const_iterator' has no member named 'first' - не знаете почему? Спасибо за помощь! - person Calihog; 21.03.2016
comment
Удаление указателей должно выполняться в деструкторе CDirectory. Поскольку result является итератором, вам нужно использовать result->first->getFileName(), так как итератор действует как указатель. - person NathanOliver; 21.03.2016
comment
@NathanOliver, да, только что отладил его, и мне также нужно было изменить свой Greater() пользовательский компаратор, чтобы вернуть return (a.first->getFileSize() < b.first->getFileSize()); Теперь он работает отлично. :)) Большое спасибо за помощь! - person Calihog; 21.03.2016
comment
@Calihog Нет проблем. - person NathanOliver; 21.03.2016