Вопрос о ссылке на класс из другого исходного файла в C ++ - PullRequest
0 голосов
/ 05 октября 2018

Недавно у меня возникли некоторые «неопределенные ссылки», которые мне удалось устранить, но я не понимаю, почему решение работает.У меня есть следующий основной исходный файл:

Main.cpp:

 #include <iostream>
#include "Log.h"

    int main()
    {
        std::cout << "Hello World!" << std::endl;

        Log log;
        log.SetLevel(Log::LevelWarning);
        log.Error("Hello!");
        log.Warning("Hello!");
        log.Info("Hello!");

        std::cin.get();
    }

, который ссылается на класс, объявленный в отдельном исходном файле:

Log.cpp:

#include <iostream>

class Log
{
public:
    enum Level
    {
       LevelError, LevelWarning, LevelInfo 
    };

private:
    Level m_LogLevel = LevelInfo;

public:
    void SetLevel (Level level)
    {
        m_LogLevel = level;
    }

    void Error (const char* message)
    {
        if (m_LogLevel >= LevelError)
            std::cout << "[ERROR]: " << message << std::endl;
    }

    void Warning (const char* message)
    {
        if (m_LogLevel >= LevelWarning)
            std::cout << "[WARNING]: " << message << std::endl;
    }

    void Info (const char* message)
    {
        if (m_LogLevel >= LevelInfo)
            std::cout << "[INFO]: " << message << std::endl;
    }
};

Log.h:

#pragma once

class Log
{
public:
    enum Level { LevelError, LevelWarning, LevelInfo };

private:
    Level m_LogLevel;

public:
    void SetLevel (Level);
    void Error (const char*);
    void Warning (const char*);
    void Info (const char*);
};

Приведенный выше код дает мне ошибки компоновщика "неопределенная ссылка на Log :: ..." для всех членов класса Log, вызываемого в Main.cpp.В поисках я в конце концов нашел комментарий, в котором говорилось что-то вроде «статические члены и функции должны быть инициализированы», что дало мне идею добавить следующее:

void Init()
{
    Log log;
    log.SetLevel(Log::LevelInfo);
    log.Error("NULL");
    log.Warning("NULL");
    log.Info("NULL");
}

в мой файл Log.cpp.Это удивительным образом решает проблему, и проект успешно собирается, но эти члены не объявлены как статические, и поэтому я не понимаю, почему это работает, или даже если это правильное решение.

Я использую gccв Linux и компиляции с помощью "g ++ Main.cpp Log.cpp -o main".Исходные файлы находятся в той же папке.

Ответы [ 2 ]

0 голосов
/ 05 октября 2018

Ваш код неправильно организован.У вас не должно быть двух разных class Log { ... }; содержимого для одного и того же класса.

Main.cpp должен знать содержимое class Log, поэтому (единственное) определение class Log должно быть в вашемзаголовочный файлЭто оставляет вопрос об определениях функций-членов класса.Существует три способа определения функции-члена класса:

  1. Внутри определения класса (которое находится в заголовке).

Это то, что вы пытались сделать в своем Журнале.файл cpp.Если вы определите все члены в определении класса в Log.h, тогда вам вообще не понадобится файл Log.cpp.

Вне определения класса с ключевым словом inline в заголовочном файле.

Это будет выглядеть так:

// Log.h
class Log
{
    // ...
public:
    void SetLevel(Level level);
    // ...
};

inline void Log::SetLevel(Level level)
{
    m_LogLevel = level;
}
Вне определения класса, без ключевого слова inline, в исходном файле.

Это будет выглядеть так:

// Log.h
class Log
{
    // ...
public:
    void SetLevel(Level level);
    // ...
};

// Log.cpp
#include "Log.h"

void Log::SetLevel(Level level)
{
    m_LogLevel = level;
}

Примечание. Log.cpp включает в себя Log.h, так что компилятор видит определение класса до того, как вы попытаетесь определить его члены.

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

В некоторых случаях может быть целесообразно определить класс внутри исходного файла * .cpp - но это означает, что он может быть толькоиспользуется из этого файла.

0 голосов
/ 05 октября 2018

c++ не является java или c#.Эта конструкция вообще не будет генерировать никакого кода:

class X
{
public:
     void foo()
     {
         std::cout << "Hello, world"<< std::endl;
     }
};

Да, в Java после компиляции вы получите X.class, который вы можете использовать.Однако в c ++ это ничего не дает.

proof:

#include <stdio.h>

class X
{
    void foo()
    {
        printf("X");
    }
};

$ gcc -S main.cpp
$ cat main.s
    .file   "main.cpp"
    .ident  "GCC: (GNU) 4.9.3"
    .section        .note.GNU-stack,"",@progbits

В c ++ для компиляции чего-либо требуется что-то, кроме «определений».

Если выЧтобы эмулировать поведение Java-подобного компилятора, сделайте следующее:

class X
{
public:
    void foo();
};

void X::foo()
{
    std::cout << "Hello, world"<< std::endl;
}

, это сгенерирует объектный файл, содержащий void X::foo().

proof:

$ gcc -c test.cpp
$ nm --demangle test.o
0000000000000000 T X::foo()

Другой вариант:конечно, используйте встроенный метод, как и вы, но в этом случае вам нужно #include весь "Log.cpp" в ваш "Main.cpp".

В c ++ компиляция выполняется "единицами перевода" вместоклассы.Одна единица (скажем, .cpp) создает один объектный файл (.o).Такой объектный файл содержит машинные инструкции и данные.

Компилятор не видит ничего, кроме компилируемого модуля перевода.

В отличие от Java, когда компилируется main.cpp, компилятор видит только то, что# включены в main.cpp и сам main.cpp.Следовательно, компилятор не видит содержимое Log.cpp в настоящее время.

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

Класс со встроенной функцией (как в первом примере) не определяет машинные инструкции или данные.

Для встроенных членов машинных инструкций классабудет сгенерировано только при их использовании.

Поскольку вы используете членов своего класса в main.cpp, который находится за пределами единицы перевода Log.cpp во время компиляции компилятора Log.cpp не генерирует для них никаких машинных инструкций.

Проблема Одно правило определения - другое.

...