Проблема связывания "статических" методов в C ++ - PullRequest
6 голосов
/ 22 сентября 2008

Я хочу вызвать несколько «статических» методов класса CPP, определенного в другом файле, но у меня возникают проблемы со связыванием. Я создал контрольный пример, воссоздающий мою проблему, и код для него приведен ниже.

(Я совершенно новичок в C ++, я из Java, и я немного знаком с C.)

// CppClass.cpp
#include <iostream>
#include <pthread.h>

static pthread_t thread;
static pthread_mutex_t mutex;
static pthread_cond_t cond;
static int shutdown;

using namespace std;

class CppClass
{
public:
        static void Start()
        {
                cout << "Testing start function." << endl;
                shutdown = 0;
                pthread_attr_t attr;
                pthread_attr_init(&attr);
                pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
                pthread_mutex_init(&mutex, NULL);
                pthread_cond_init(&cond, NULL);

                pthread_create(&thread, &attr, run_thread, NULL);
        }

        static void Stop()
        {
                pthread_mutex_lock(&mutex);
                shutdown = 1;
                pthread_cond_broadcast(&cond);
                pthread_mutex_unlock(&mutex);
        }

        static void Join()
        {
                pthread_join(thread, NULL);
        }
private:
        static void *run_thread(void *pthread_args)
        {
                CppClass *obj = new CppClass();
                pthread_mutex_lock(&mutex);
                while (shutdown == 0)
                {
                        struct timespec ts;
                        ts.tv_sec = time(NULL) + 3;
                        pthread_cond_timedwait(&cond, &mutex, &ts);
                        if (shutdown)
                        {
                                break;
                        }
                        obj->display();
                }
                pthread_mutex_unlock(&mutex);
                pthread_mutex_destroy(&mutex);
                pthread_cond_destroy(&cond);
                pthread_exit(NULL);
                return NULL;
        }

        void display()
        {
                cout << " Inside display() " << endl;
        }
};

// main.cpp
#include <iostream>
/* 
 * If I remove the comment below and delete the
 * the class declaration part, it works.
 */
// #include "CppClass.cpp"
using namespace std;

class CppClass
{
public:
        static void Start();
        static void Stop();
        static void Join();
};

int main()
{
        CppClass::Start();
        while (1)
        {
                int quit;
                cout << "Do you want to end?: (0 = stay, 1 = quit) ";
                cin >> quit;
                cout << "Input: " << quit << endl;
                if (quit)
                {
                        CppClass::Stop();
                        cout << "Joining CppClass..." << endl;
                        CppClass::Join();
                        break;
                }
        }
}

Когда я пытался скомпилировать, я получаю следующую ошибку:

$ g++ -o go main.cpp CppClass.cpp -l pthread
/tmp/cclhBttM.o(.text+0x119): In function `main':
: undefined reference to `CppClass::Start()'
/tmp/cclhBttM.o(.text+0x182): In function `main':
: undefined reference to `CppClass::Stop()'
/tmp/cclhBttM.o(.text+0x1ad): In function `main':
: undefined reference to `CppClass::Join()'
collect2: ld returned 1 exit status

Но если я удаляю объявление класса в main.cpp и заменяю его на #include "CppClass.cpp", оно работает нормально. По сути, я хочу поместить эти объявления в отдельный файл .h и использовать его. Я что-то упустил?

Спасибо за помощь.

Ответы [ 5 ]

32 голосов
/ 22 сентября 2008

Очевидно, что вы пришли из Java, потому что вы еще не поняли концепцию заголовочных файлов. В Java процесс определения чего-либо обычно выполняется одним куском. Вы объявляете и определяете одновременно. В C / C ++ это двухэтапный процесс. Объявление что-то говорит компилятору "что-то существует с этим типом, но я расскажу вам позже, как это на самом деле реализовано". Определение что-то дает компилятору фактическую часть реализации. Заголовочные файлы используются в основном для объявлений, файлы .cpp для определений.

Заголовочные файлы предназначены для описания «API» классов, но не для их реального кода. Можно включить код в заголовок, который называется встраиванием в заголовок. Вы вставили все в CppClass.cpp (что не очень хорошо, включение заголовка должно быть исключением), а затем вы объявляете свой класс в main.cpp AGAIN, который является двойным объявлением в C ++. Встраивание в тело класса приводит к дублированию кода каждый раз, когда вы используете метод (это только звучит безумно. Подробности см. В разделе C ++ faq по встраиванию .)

Включение двойного объявления в ваш код приводит к ошибке компилятора. Выход из кода класса компилируется, но выдает ошибку компоновщика, потому что теперь у вас есть только заголовочное объявление класса в main.cpp. Компоновщик не видит кода, реализующего ваши методы класса, поэтому появляются ошибки. В отличие от Java, компоновщик C ++ НЕ будет автоматически искать объектные файлы, которые он хочет использовать. Если вы используете класс XYZ и не предоставляете ему объектный код для XYZ, он просто потерпит неудачу.

Пожалуйста, взгляните на статью заголовочного файла Википедии и Шаблоны заголовочных файлов (ссылка также находится внизу статьи Википедии и содержит больше примеров)

Короче говоря:

Для каждого класса создайте файлы NewClass.h и NewClass.cpp.

В файле NewClass.h напишите:

class NewClass {
public:
   NewClass();
   int methodA();
   int methodB();
}; <- don't forget the semicolon

В файле NewClass.cpp напишите:

#include "NewClass.h"

NewClass::NewClass() {
  // constructor goes here
}

int NewClass::methodA() {
  // methodA goes here
  return 0;
}

int NewClass::methodB() {
  // methodB goes here
  return 1;
}

В main.cpp напишите:

#include "NewClass.h"

int main() {
  NewClass nc;
  // do something with nc
}

Чтобы связать все это вместе, сделайте

g ++ -o NewClassExe NewClass.cpp main.cpp

(просто пример с gcc)

9 голосов
/ 22 сентября 2008

Вы определяете класс дважды, что, я уверен, не работает.

Попробуйте что-то вроде этого:

Первый заголовочный файл CppClass.h:

// CppClass.h
using namespace std;

class CppClass
{
public:
    static void Start();
    static void Stop();
    static void Join();
private:
    void *run_thread(void *pthread_args);
    void display();
};

Затем файл CppClass.cpp, реализующий его:

// CppClass.cpp
#include <iostream>
#include <pthread.h>
#include "CppClass.h"

using namespace std;

void CppClass::Start()
{
    /* method body goes here */
}
void CppClass::Stop()
{
    /* method body goes here */
}
void CppClass::Join()
{
    /* method body goes here */
}
void *CppClass::run_thread(void *pthread_args)
{
    /* method body goes here */
}
void CppClass::display() {
    /* method body goes here */
}

Тогда ваш основной файл:

// main.cpp
#include "CppClass.h"

int main()
{
    /* main method body here */
}

Я считаю, что вызов g ++ будет таким же.

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

Надеюсь, это имело смысл ...

2 голосов
/ 22 сентября 2008

Я думаю, вы хотите сделать что-то вроде:

g ++ -c CppClass.cpp g ++ -c main.cpp g ++ -o перейти на main.o CppClass.o

Это должно решить это.

1 голос
/ 22 сентября 2008

создайте файл .h с определением класса в нем, а затем #include этот файл в ваши 2 файла.

0 голосов
/ 22 сентября 2008

Конечно, похоже, что компоновщик не подхватывает ваш второй исходный файл.

...