Неопознанный идентификатор c++, но он идентифицирован

наличие этой проблемы со страной или континентом, не идентифицированным в следующем коде для файла Map.h

Карта.ч:

#ifndef MAP_H
#define MAP_H
#include<string>
#include<vector>
#include<iostream>
#include<fstream>
//#include "Country.h"

using namespace std;
class Map{

private:
    vector<Country> countries;
    vector<Continent> continents;
    vector<vector<int> > adjacents;
    string author;
    string image;
    string wrap;
    string scroll;
    string warn;
public:
    Map(ifstream);
    Map();
    void save();
    void setAdjacent(Country&, Country&);
    void placeWithin(Continent&, Country&);
    int numOfCountries();
    int numOfContinents();
    bool verify();
    bool isAdjacent(Country*, Country&);
    bool hasAdjacent(Country*);
    bool hasCountry(Continent&);

};
#endif

Независимо от того, добавляете ли вы закомментированное включение, я все равно получаю сообщение об ошибке.

Вот еще два файла

Континент.h:

#ifndef CONTINENT_H
#define CONTINENT_H

#include "Map.h"
using namespace std;
class Continent
{
private:
    string name;
    int bonus;
    vector<Country> countries;
public:
    void addCountry(Country&);
    int getOwner();
    int getBonus();
    int getSize();
    string getName();
    bool hasCountry(Country&);


};
#endif

Страна.ч:

#ifndef COUNTRY_H
#define COUNTRY_H
#include "Continent.h"


using namespace std;

class Country{
    private:
        static int nextCountryNumber;
        int countryNumber;
        Map map;
        string name;
        int x, y;
        Continent continent;
        vector<Country> adjacents;
    public:
        Country(string, int, int, Continent&, Map&);
        string getName();
        int getX();
        int getY();
        Continent getContinent();
        bool isAdjacentTo(Country&);
        bool hasAdjacent();
        void addAdjacent(Country&);
        string toString();
};
#endif

Я думаю, что это своего рода циклическая ссылка, но я не могу найти, где это так...


person Demo Kioussis    schedule 05.11.2015    source источник
comment
почему вы включаете Map.h в Continent.h?   -  person Xiaotian Pei    schedule 05.11.2015
comment
@XiaotianPei, потому что Map используется в Country, а Country.h включает Continent.h, который включает Map.h   -  person GreatAndPowerfulOz    schedule 05.11.2015
comment
Обычно вы используете предварительные объявления вместо include в заголовках. В противном случае вы можете столкнуться с циклическими зависимостями. И что конкретно за ошибка? Не могли бы вы вставить это здесь?   -  person personjerry    schedule 05.11.2015
comment
Не ваша основная проблема, но Map(std::ifstream); может доставить вам неприятности. Передача потоков по значению не разрешалась до C++11, и некоторые основные компиляторы медленно реализовывали эту функцию. Map(std::ifstream &) было бы более обычным.   -  person M.M    schedule 05.11.2015


Ответы (4)


Как правило, в файлы .h вы не хотите включать другие файлы заголовков. Это просто объявления, поэтому им не нужно знать, как выглядят другие классы. Удалите #include "blah.h" и вместо этого напишите предварительные объявления class blah; там, где они вам нужны. Это случай, ЕСЛИ вы храните объект blah непосредственно в своем классе, и в этом случае вам нужно включить blah.h, потому что компилятору необходимо знать, насколько велик этот объект. Вы можете избежать этих включений, если вместо этого вы храните указатель на объект вместо объекта напрямую (и это действительно более распространенная практика, поскольку вам не нужно копировать все данные при копировании конструкции).

РЕДАКТИРОВАТЬ: В качестве примечания вы обычно хотите включать только необходимые #includes (а именно iostream), а также не использовать using namespace std; в своих заголовках, потому что другой файл, включая ваш заголовок, может не захотеть использовать пространство имен std. Вместо этого сделайте это в файле реализации. Я внес соответствующие изменения ниже.

РЕДАКТИРОВАТЬ 2: Кроме того, имейте в виду, что map - это тип структуры данных в std, поэтому будьте осторожны, когда вы называете вещи map (я бы посоветовал использовать другое имя).

РЕДАКТИРОВАТЬ 3: Как указано в комментариях, стандартные контейнеры требуют полного объявления содержащихся в них объектов, поэтому всякий раз, когда у вас есть vector<blah>, вы должны включать blah.h. Также учтите следующее: проблематично включить объект blah в его собственное объявление. Поскольку объект содержит экземпляр самого себя, это рекурсивно, так сколько места нужно выделить? Вы можете исправить это, указав pointer-to-blah в blah (указатель - это просто фиксированный размер int). По той же причине для класса Country проблематично содержать класс vector<Country>. ЭТО можно решить, заменив vector<Country> на vector<Country*>. Поэтому, чтобы сделать их технически компилируемыми по стандарту C++ и очистить дизайн в отношении включения других заголовков, я изменил переменные-члены, ссылающиеся на Country, Continent и Map, на их соответствующие указатели, изменение, которое вам нужно отразить. в файлах реализации. Также я изменил Map(std::ifstream); на Map(std::ifstream &);, как было предложено в другом комментарии, так как я серьезно сомневаюсь, что вы намеревались скопировать сюда ifstream.

Код:

Карта.ч:

#ifndef MAP_H
#define MAP_H
#include<string>
#include<vector>
#include<fstream>

class Country;
class Continent;

class Map{

private:
    std::vector<Country*> countries; // consider pointers instead
    std::vector<Continent*> continents; // consider pointers instead
    std::vector<std::vector<int> > adjacents; // (just ints, so no pointers needed)
    std::string author;
    std::string image;
    std::string wrap;
    std::string scroll;
    std::string warn;
public:
    Map(std::ifstream &);
    Map();
    void save();
    void setAdjacent(Country&, Country&);
    void placeWithin(Continent&, Country&);
    int numOfCountries();
    int numOfContinents();
    bool verify();
    bool isAdjacent(Country*, Country&);
    bool hasAdjacent(Country*);
    bool hasCountry(Continent&);

};
#endif

Континент.h:

#ifndef CONTINENT_H
#define CONTINENT_H
#include<string>
#include<vector>

class Country;

class Continent
{
private:
    std::string name;
    int bonus;
    std::vector<Country*> countries;
public:
    void addCountry(Country&);
    int getOwner();
    int getBonus();
    int getSize();
    std::string getName();
    bool hasCountry(Country&);


};
#endif

Страна.ч:

#ifndef COUNTRY_H
#define COUNTRY_H
#include<string>
#include<vector>

class Map;
class Continent;

class Country{
    private:
        static int nextCountryNumber;
        int countryNumber;
        Map *map;
        std::string name;
        int x, y;
        Continent *continent;
        std::vector<Country*> adjacents;
    public:
        Country(std::string, int, int, Continent&, Map&);
        std::string getName();
        int getX();
        int getY();
        Continent getContinent();
        bool isAdjacentTo(Country&);
        bool hasAdjacent();
        void addAdjacent(Country&);
        std::string toString();
};
#endif
person personjerry    schedule 05.11.2015
comment
Чтобы сделать это хорошим ответом, вам нужно удалить оператор using namespace std;. Это дурной тон вообще и крайне дурной тон в заголовочном файле. - person David Hammen; 05.11.2015
comment
Ха-ха, я только что сделал эти правки. Мы думали об одном и том же. - person personjerry; 05.11.2015
comment
Вам нужно внести еще три изменения: (1) Continent.h не нужно пересылать объявления Map. (2) Continent.h необходимо передать объявление Country. (3) Country.h необходимо #include Map.h и Continent.h Посмотрите на структуры данных. Класс Continent вообще не ссылается на Map, но ссылается на Country. Класс Country имеет элементы данных типов Map и Continent, поэтому предварительного объявления недостаточно. - person David Hammen; 05.11.2015
comment
Хорошие уловы. На самом деле, Country.h также должен включать Continent :( Я привык к Objective-C, где объекты автоматически являются указателями; знаете ли вы, что в C++ обычно лучше хранить здесь через указатель? - person personjerry; 05.11.2015
comment
Это предложение неверно. std::vector не может иметь неполный тип в качестве параметра шаблона. (Несмотря на то, что некоторые компиляторы могут это принять). Код должен быть организован так, чтобы определение класса было видно в том месте, где появляется строка vector<Country> countries;, и так далее. - person M.M; 05.11.2015
comment
Ого, я этого не знал. В таком случае проблематично ли std::vector<Country> adjacents; внутри Country? - person personjerry; 05.11.2015
comment
@personjerry Да, это тоже проблематично - person M.M; 05.11.2015
comment
Хорошо, я обновил пост с новой информацией. Я заменил их все на указатели, но, конечно, это требует изменения реализации (должно быть сделано для страны и определенно полезно в долгосрочной перспективе для других). - person personjerry; 05.11.2015

Я думаю, что это своего рода циклическая ссылка, но я не могу найти, где это так...

У вас гораздо хуже, чем проблема с циклическими ссылками. У вас запутанный беспорядок. Вы можете решить проблему циклических ссылок, следуя ответу @personjerry. Это не поможет решить вашу запутанную проблему беспорядка.

Последняя проблема: ваш класс Country имеет элементы данных типа Map и Continent и вектор смежных объектов Country. Ваш класс Continent имеет вектор Country объектов на этом континенте. В вашем классе Map есть векторы объектов Country и Continent. Country с именем Foo в элементе данных countries объекта Map не является тем же объектом, что и Country с именем Foo в элементе данных countries объекта Continent. Хуже того, все ваши геттеры возвращают копии. У вас есть копии за копиями за копиями. Это запутанный беспорядок.

Способ решения этой проблемы до C++11 состоял в использовании указателей и тщательном продумывании того, кому что принадлежит. В вашем случае класс Map кажется первичным, поскольку это класс, созданный из входного потока. Ссылки на Continent и Map в классе Country должны быть указателями или ссылками. Векторы объектов Country в классах Country и Continent должны быть векторами указателей.

Более современный подход заключается в использовании интеллектуальных указателей (в вашем случае общих указателей). Нужно еще немного подумать о том, кому что принадлежит, и здесь это очень актуально. Циклические ссылки представляют некоторую проблему в отношении общих указателей. У вас есть большой потенциал для циклических ссылок с этой структурой.


В сторону: использование using namespace std; широко считается дурным тоном. Почти повсеместно считается крайне плохим тоном, когда эта конструкция находится в заголовочном файле.

person David Hammen    schedule 05.11.2015

Для начала в Map.h нужно перенаправить объявления классов Country и Continent

class Country;
class Continent;

до объявления class Map.

person GreatAndPowerfulOz    schedule 05.11.2015

Есть некоторые проблемы в дизайне вашего кода. Существуют рекурсивные определения контейнеров.

Первое, о чем следует помнить, это то, что std::vector должен иметь полный тип в качестве параметра шаблона. Это означает, что у вас не может быть класса, который содержит вектор самого себя; поэтому вам придется переосмыслить дизайн:

class Country
{
    // ...
    vector<Country> adjacents;

Аналогичные комментарии относятся и к другим вашим векторам: недостаточно предварительно объявить класс, а затем объявить его вектор; вы не можете иметь vector<Country> внутри class Map, если только class Country не определено полностью. Чего не может быть, потому что class Country также содержит Map !


Однако, глядя на остальную часть вашего дизайна классов и векторов, это больше похоже на то, что сделал бы кодировщик Java или C#, где контейнер содержит ссылки на объекты, а не объекты.

В частности, Country, содержащее Map, не имеет особого смысла. В типичном дизайне будет только одна карта, а карта содержит множество стран. Но в вашем дизайне у каждой страны есть собственная карта, полностью отдельная от карты любой другой страны (и полностью отдельная от любой глобальной карты).

Чтобы поделиться ссылками на один экземпляр среди многих пользователей C++, правильно использовать shared_ptr в качестве контейнера для объекта, на который делается ссылка. (Это также ограничивает то, как вы размещаете объекты — вы должны создавать их с помощью make_shared, а не с прямым объявлением объекта).

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

Альтернативным решением для страны, требующей хранения списка стран, может быть либо список идентификаторов/названий стран, которые вы просматриваете при необходимости; или использовать нестандартный контейнер, который разрешает объявление с неполным типом. В библиотеке Boost Container есть некоторые из них.

person M.M    schedule 05.11.2015