Циклическая зависимость C++ с защитой заголовков и упреждающими объявлениями

У меня есть простой Vec3 класс и простой xMath класс.

Класс xMath должен изменить Vec3. Есть только 2 файла Vec3.hpp и xMath.hpp (и мне не разрешено создавать другие файлы).

  • оба класса находятся в одном пространстве имен
  • есть защита заголовков
  • на обоих есть включения
  • xMath не признается
  • использование форвардных объявлений не работает, пробовал

Использование Visual Studio 2019 Community Edition.

Получение ошибок:

  • ошибка C2027: использование неопределенного типа 'ns::xMath'Vec3.hpp

Используя предварительные объявления, все равно получаем ошибки:

  • ошибка C2653 Vec3 не является классом или именем пространства имен
  • ошибка C2027: использование неопределенного типа 'ns::Vec3'

Вот файлы.

//Vec3.hpp
#ifndef VEC3_H
#define VEC3_H

#include "xMath.hpp"

namespace ns
{
    //class xMath; //forward declaration does not work either..

    struct Vec3
    {
    public:

        float x;

        //constructor
        Vec3(float x) { this.x = x; }

        static float Change(Vec3* v)
        {
            v->x = xMath::Change(v);

            return t;
        }

    };
}

#endif
//xMath.hpp
#ifndef XMATH_H
#define XMATH_H

#include "Vec3.hpp"

namespace ns
{
    //class Vec3; //forward declaration does not work either..

    static class xMath
    {
    public:

        static float Change (Vec3* v)
        {
            return v->x;
        }
    };
}

#endif

Удалите #include "Vec3.hpp" и переместите его в конец файла xMath.hpp (прямо перед #endif). Затем добавьте предварительное объявление Vec3 внутри пространства имен ns. Затем измените функцию xMath::Change (это очень плохое название для чего-то, что на самом деле ничего не меняет), чтобы она была просто объявлением. И, наконец, определите (реализуйте) функцию xMath::Change после включения Vec3.hpp.

Some programmer dude 30.09.2023 11:03

"и мне не разрешено создавать другие файлы" что вам разрешено менять? Например, почему xMath является классом, если у него есть только статические методы? Может ли это быть просто вложенное пространство имен?

Homer512 30.09.2023 11:05

Хорошо, я на самом деле портирую код с другого языка, чтобы не создавать оболочку. Цель метода Change() — зависеть только от Vec3, не нужно усложнять пример. Я мог бы создать классическую пару .cpp и .h, но надеюсь, что смогу портировать код, используя только .h или .hpp, поскольку файлов более 500. Также было бы полезно зеркалировать как можно больше других файлов.

maverick1999 30.09.2023 11:12

@Homer512 Homer512 Да, xMath может быть просто вложенным пространством имен, но я ищу общее решение, потому что есть и другие классы, у которых не все методы являются статическими.

maverick1999 30.09.2023 11:14

Меня беспокоит вопрос «переноса с другого языка». Прямой «перевод» редко работает хорошо. Неважно, письменный, устный или языки программирования. Вместо этого запишите подробное описание поведения программы, которую вы хотите «портировать» или «перевести» (если она еще не существует, например, проектный документ), а затем переопределите ее на целевом языке, используя все возможности целевой язык.

Some programmer dude 30.09.2023 12:20
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
63
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Эти требования очень странные. Обычно мы помещаем реализацию в отдельный файл, включающий оба заголовка.

Однако мы можем сделать что-то подобное внутри заголовков:

#ifndef XMATH_H
#define XMATH_H

// First the declarations:

namespace ns
{
    class Vec3;

    class xMath
    {
    public:
        static float Change (Vec3* v);
    };
}


// Now the implementation (which would normally be in xMath.cpp):

#include "Vec3.hpp"

float ns::xMath::Change (Vec3* v)
{
    return v->x;
}

#endif

По крайней мере, отчасти причина, по которой упреждающие объявления не сработали в предыдущем примере, заключалась в том, что Vec3 был заранее объявлен как class, но полностью объявлен как struct. Форвард-объявления должны соответствовать фактическим.

Ошибка C2027 возникает в коде, который обращается к членам класса, когда доступно только предварительное объявление. Есть существующие вопросы за 2009 , 2015 и 2017.

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

Как предположили Тоби Спейт и Пол Сандерсон, способ обеспечить доступ обоих классов к членам друг друга заключается в реализации методов классов вне каждого класса. Последовательность — это предварительные объявления, объявления классов и, наконец, реализации.

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

Такое предварительное объявление компилируется:

namespace ns
{
    class XMath;

    class Vec3
    {
    public:
        float m_x;

        Vec3(float x)
                : m_x(x)
                {}

Другие вопросы по теме