Странная проблема с включением. Неизвестный спецификатор переопределения

#c

Вопрос:

У меня есть 3 класса (Объект, Компонент, Преобразование)и одно пространство имен(Движок). Пространство имен движка:

 #pragma once

namespace Engine {

#include <GLM/glm.hpp>

#include "../Objects/Object.h"
#include "../Objects/Components/Component.h"
#include "../Objects/Components/Transform.h"

}
 

Класс объектов:

 #pragma once

#include <iostream>
#include <GLM/gtc/matrix_transform.hpp>

#include "../Rendering/Loader.h"

#include "../Namespaces/Engine.h"

class Object {

public:
    glm::vec3 position;
    glm::vec3 rotation;
    glm::vec3 scale;

    Object();
    Object(glm::vec3 position, glm::vec3 rotation, glm::vec3 scale);
};
 

Класс компонентов:

 #pragma once

class Component {

public:
    unsigned int id;

};
 

Класс преобразования:

 #pragma once

#include "../../Namespaces/Engine.h"

using namespace Engine;

class Transform : public Component {

public:
    Object object; <-- unknown override specifier

    glm::vec3 position;
    glm::vec3 rotation;
    glm::vec3 scale;

};
 

Я знаю, что проблема в том, что двигатель.h включен в Object.h, а Object.h включен в движок.h чтобы объектная переменная в преобразовании не знала о ее существовании, но почему и как это исправить, не удаляя объект include из движка.h так как мне нужно сделать объект и другие компоненты легко доступными, просто включив движок.h. Я знаю, что это ошибка компилятора, но есть ли какой-то способ исправить ее, не включая объект.h отдельно в другие файлы ?

Комментарии:

1. Извините, я не понимаю, почему вам нужно включить Engine.h это перед Object началом занятий.

2. Включить цикл: преобразование.h <-> двигатель.h

3. Задавая вопросы об ошибках сборки, всегда включайте полное и полное копирование-вставку (в виде текста) ошибок, которые вы получаете.

4. Я думаю, ответ зависит от того, чего на самом деле хочет достичь ваш проект. Существует много способов разорвать цикл на графике; какой из них лучше, можете знать только вы.

5. Это первое namespace с #include директивами внутри выглядит очень подозрительно и совершенно неправильно.

Ответ №1:

TL;DR избегайте циркулярных #include директив, как чумы.

Помните, что препроцессор просто выполняет замену текста. Когда он сталкивается с #include директивой, он буквально заменяет эту директиву (предварительно обработанным) текстом включенного файла.

В этом случае фактический результат будет варьироваться в зависимости от того, какой именно заголовок вы включаете. Ваша ошибка будет возникать каждый раз, когда вы попытаетесь напрямую включить Object.h.

Имея это в виду, давайте возьмем на себя роль препроцессора, чтобы понять, в чем проблема. Давайте начнем предварительную обработку объекта.h:

 #pragma once
 

Мы столкнулись с нашей первой директивой. Это говорит нам о том, чтобы отметить, что мы уже включили этот файл в этот блок компиляции, и пропустить его, если нам скажут включить его снова. Мы не будем генерировать никаких выходных данных для этой директивы.

 #include <iostream>
#include <GLM/gtc/matrix_transform.hpp>

#include "../Rendering/Loader.h"
 

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

 // contents of iostream
// contents of GLM/gtc/matrix_transform.hpp

// contents of Loader.h

#include "../Namespaces/Engine.h"
 

Нас попросили включить содержимое Engine.h. Для этого нам сначала нужно будет предварительно обработать его.

 #pragma once

namespace Engine {

#include <GLM/glm.hpp>
 

Same as before, nothing too interesting here. Do note that including GLM/glm.hpp into the Engine namespace will likely cause compiler/linker errors later in the toolchain. We’re just a preprocessor though, so we don’t care about any of that.

 namespace Engine {

// contents of GLM/glm.hpp

#include "../Objects/Object.h"
 

Another include directive. We were told using #pragma once to skip over future includes of Object.h though, so we won’t produce any output for this directive.

 namespace Engine {

// contents of GLM/glm.hpp

#include "../Objects/Components/Component.h"
 

This one’s pretty simple, we’ll just preprocess and include Component.h.

 namespace Engine {

// contents of GLM/glm.hpp

class Component {

public:
    unsigned int id;

};
#include "../Objects/Components/Transform.h"
 

Другие включают директиву. Давайте начнем предварительную обработку преобразования.h

 #pragma once

#include "../../Namespaces/Engine.h"
 

Нас попросили включить Engine.h, но a уже посоветовал нам #pragma once пропустить будущие включения этого файла, поэтому мы не будем выдавать никаких выходных данных для этого. Это означает, что все предварительно обработанное преобразование.h будет выглядеть следующим образом:

 using namespace Engine;

class Transform : public Component {

public:
    Object object;

    glm::vec3 position;
    glm::vec3 rotation;
    glm::vec3 scale;

};
 

Теперь, когда мы закончили с Transform.h, мы можем вернуться на уровень выше и вставить это в Engine.h

 namespace Engine {

// contents of GLM/glm.hpp

class Component {

public:
    unsigned int id;

};

using namespace Engine;

class Transform : public Component {

public:
    Object object;

    glm::vec3 position;
    glm::vec3 rotation;
    glm::vec3 scale;

};

}
 

И это конец Engine.h, поэтому мы можем вернуться на уровень выше и вставить это в Object.h. В Object.h нет никаких дальнейших директив препроцессора, поэтому результат препроцессора для этого файла будет выглядеть следующим образом

 // contents of iostream
// contents of GLM/gtc/matrix_transform.hpp

// contents of Loader.h

namespace Engine {

// contents of GLM/glm.hpp

class Component {

public:
    unsigned int id;

};

using namespace Engine;

class Transform : public Component {

public:
    Object object;

    glm::vec3 position;
    glm::vec3 rotation;
    glm::vec3 scale;

};

}

class Object {

public:
    glm::vec3 position;
    glm::vec3 rotation;
    glm::vec3 scale;

    Object();
    Object(glm::vec3 position, glm::vec3 rotation, glm::vec3 scale);
};
 

Теперь вы, возможно, сможете определить проблему. На Object класс ссылаются Transform до того, как он определен. Обратите внимание, что он также не определен в Engine пространстве имен, как вы, вероятно, ожидаете.


Способ решить эту проблему-убедиться, что на вашем графике включения нет циклов. Если вам нужен большой мета-заголовок, который пользователи могут включить, чтобы получить всю функциональность вашей библиотеки, это нормально, но ничто в вашей библиотеке не должно включать этот заголовок. Вы также не должны заключать свои заголовки в пространство имен извне. Это никогда не сработает. Также обратите внимание, что у этого #pragma once есть проблемы, и стандартные #ifndef / #define включенные охранники, как правило, более надежны. #pragma once это нормально, но ИМО лучше всего этого избегать. Вы также должны отсортировать пути включения вашего проекта, чтобы вам не нужно было использовать относительные пути.

Все это говорит о том, что нечто большее, чем это, сделает свое дело. Это позволит потребителям этой библиотеки просто #include <Engine.h> получить все или включить отдельные заголовки, чтобы получить только те части, которые они хотят:

Объект.h

 #ifndef MYLIB_OBJECT_H
#define MYLIB_OBJECT_H

#include "MyLib/Rendering/Loader.h"

#include <GLM/glm.hpp>
#include <GLM/gtc/matrix_transform.hpp>

#include <iostream>

namespace Engine {

class Object {

public:
    glm::vec3 position;
    glm::vec3 rotation;
    glm::vec3 scale;

    Object();
    Object(glm::vec3 position, glm::vec3 rotation, glm::vec3 scale);
};

}

#endif
 

Компонент.h

 #ifndef MYLIB_COMPONENT_H
#define MYLIB_COMPONENT_H

namespace Engine {

class Component {

public:
    unsigned int id;

};

}

#endif
 

Трансформация.h

 #ifndef MYLIB_TRANSFORM_H
#define MYLIB_TRANSFORM_H

#include "MyLib/Object/Object.h"
#include "MyLib/Object/Components/Component.h"

#include <GLM/glm.hpp>

namespace Engine {

class Transform : public Component {

public:
    Object object;

    glm::vec3 position;
    glm::vec3 rotation;
    glm::vec3 scale;

};

}

#endif
 

Двигатель.ч

 // Note, #include <GLM/glm.hpp> has been moved to individual headers

#include "MyLib/Objects/Object.h"
#include "MyLib/Objects/Components/Component.h"
#include "MyLib/Objects/Components/Transform.h"
 

Ответ №2:

Вы пытаетесь включить пространство имен using в заголовок вашего движка, поэтому ключевое слово using, передаваемое несколько раз, в заголовке вашего трансформатора не распознает объект Engine::. избавьтесь от строки using namespace, и она будет работать

Комментарии:

1. Я попытался удалить строку «использование движка пространства имен» в Transform.h, и она выдает ту же ошибку, я даже обнаружил, что объект класса по какой-то причине отсутствовал в пространстве имен движка, и когда я немного изменил класс, он был частью пространства имен, но теперь у меня есть дополнительная ошибка в Transform.h, в которой говорится, что объект не является частью пространства имен движка, даже если он является

2. в нем говорится, что объект не является частью пространства имен движка

Ответ №3:

Я не очень разбираюсь в C , но мне кажется, что следует использовать #define , #ifdef , #ifndef .

Пример:

A. h

 #ifndef A_h
#define A_h
#include "B.h"
class A
{
public:
    void func(B *param);
};
#endif // A_h
 

B. h

 #ifndef B_h
#define B_h
 
#include "A.h"
class B
{
public:
    void func(A *param);
};
#endif // B_h
 

Комментарии:

1. Нет, дело не в этом. OP достигает точно такого же эффекта при использовании #pragma once . Хотя это не является частью стандартного C , все современные компиляторы поддерживают #pragma once его .

2. Это все еще имеет циклическую зависимость. Вместо #include "B.h" этого вы должны просто переслать объявление class B; и сделать то же самое для A .

3. Я использую Visual studio 2019, и там #pragma один раз делает то же самое, но это работает только в некоторых IDE, например, это не работает в коде::Блоки, я думаю

4. @ChrisHusky CB по умолчанию использует GCC в качестве компилятора и #pragma once работает с GCC.