Разделение класса C ++ на файлы теперь не компилируется

Я учу себя писать классы на C ++, но не могу выполнить компиляцию. Если вы поможете мне понять не только как, но и почему, я буду очень признателен. Заранее спасибо! Вот мои три файла:

make_pmt.C

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

using namespace std;


int main() {
    CPMT *pmt = new CPMT;
    pmt->SetVoltage(900);
    pmt->SetGain(2e6);

    double voltage = pmt->GetVoltage();
    double gain= pmt->GetGain();

    cout << "The voltage is " << voltage
         << " and the gain is " << gain << "." <<endl;

    return 0;
}

pmt.C

#include "pmt.h"

using namespace std;

class CPMT {
    double gain, voltage;
    public:
        double GetGain() {return gain;}
        double GetVoltage() {return voltage;}

        void SetGain(double g) {gain=g;}
        void SetVoltage(double v) {voltage=v;}
};

pmt.h

#ifndef PMT_H
#define PMT_H 1

using namespace std;

class CPMT {
    double gain, voltage;
    public:
        double GetGain();
        double GetVoltage();

        void SetGain(double g);
        void SetVoltage(double v);
};

#endif

И для справки я получаю ошибку компоновщика (верно?):

Undefined symbols:
  "CPMT::GetVoltage()", referenced from:
      _main in ccoYuMbH.o
  "CPMT::GetGain()", referenced from:
      _main in ccoYuMbH.o
  "CPMT::SetVoltage(double)", referenced from:
      _main in ccoYuMbH.o
  "CPMT::SetGain(double)", referenced from:
      _main in ccoYuMbH.o
ld: symbol(s) not found
collect2: ld returned 1 exit status

person physicsmichael    schedule 27.04.2010    source источник
comment
Размещение директив using в заголовках обычно является плохой идеей, потому что эта директива применяется к любому файлу, который включает заголовок. В вашем случае любой файл, который включает pmt.h - даже косвенно - получает все пространство имен std в глобальное пространство имен.   -  person Gareth Stockwell    schedule 27.04.2010
comment
Я согласен с Гаретом. См. Здесь: stackoverflow.com/questions/2712076, почему плохая идея быть using namespace std; (или любым другим, для этого вообще не имеет значения.   -  person sbi    schedule 27.04.2010


Ответы (6)


Сначала немного о систематике.
Это

class CPMT {
    public:
        double GetGain();
        // ...
};

определяет класс без определения функций-членов. Этот

class CPMT {
    public:
        double GetGain() {return gain;}
        // ...
};

определяет тот же класс, а также определяет его функции-члены (неявно) встроенными. Этот

double CPMT::GetGain() {return gain;}
// ...

определяет функции-члены (не встроенные).

Теперь, если вы хотите отделить реализацию от интерфейса, ваш заголовок должен определять класс, а ваш файл реализации должен определять его функции-члены. Итак, определение чистого класса

class CPMT {
    public:
        double GetGain();
        //...
};

переходит в файл заголовка, а реализация

double CPMT::GetGain() {return gain;}
// ...

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

Есть два способа встроить функцию-член. Один из них - определить его в определении своего класса.

class CPMT {
    public:
        double GetGain() {return gain;}
        // ...
};

что неявно делает это inline. Другой - явно встроить его

class CPMT {
    public:
        double GetGain();
        //...
};

inline double CPMT::GetGain() {return gain;}
// ...

В обоих случаях реализация должна быть в файле заголовка.

person sbi    schedule 27.04.2010
comment
Хорошая ссылка на встроенные функции: parashift.com/c++-faq -lite / inline-functions.html - person Björn Pollex; 27.04.2010
comment
+1: согласен. В настоящее время многие современные библиотеки C ++ работают только с заголовками. Я стараюсь писать как можно больше кода в заголовках и помещаю реализации только в случае необходимости. - person Eddy Pronk; 27.04.2010
comment
@Eddy: Я не согласен. Есть причины делать вещи inline и есть причины против этого. (Во-первых, многие современные библиотеки C ++ в настоящее время предназначены только для заголовков, потому что большая часть их материала - это шаблоны, которые должны находиться в заголовках. Кроме того, это случай встраивания кода в библиотеки намного сильнее, чем для кода приложения.) - person sbi; 27.04.2010
comment
@sbi: Для этого нет хороших рекомендаций, но я хочу сказать, что это ускоряет разработку. Это экономит время, поскольку вам не нужно синхронизировать заголовок и файл cpp. Если есть конкретная причина для его замены, вы можете поменять его, но это дешево. см. stackoverflow.com/questions/2174657/ < / а> - person Eddy Pronk; 27.04.2010
comment
@Eddy: Вам когда-нибудь приходилось работать над заголовком, состоящим из одного шаблона, в нескольких проектах MLoC? Что-то базовое, например обработка ошибок или манипуляции со строками, которое использовалось во всей кодовой базе? Вы изменяете реализацию некоторой вспомогательной функции для вспомогательного класса, который используется только частной реализацией какого-либо другого шаблона, и ждете 20 минут для компилятора. И тогда вы понимаете, что некоторые из ваших уважаемых коллег проигнорировали вашу документацию, просто посмотрели на код и полагались на артефакт реализации, который вы собирались изменить. Встраивание может очень серьезно замедлить разработку. - person sbi; 27.04.2010
comment
Спасибо за прекрасный ответ. Вы предоставили учебное пособие именно по той теме, на которую я надеялся получить ответ, и ваши комментарии и комментарии @Eddy Pronk также были очень полезными. Большое Вам спасибо. - person physicsmichael; 27.04.2010

pmt.C будет выглядеть так:

#include "pmt.h"

using namespace std;

double CPMT::GetGain() {return gain;}
double CPMT::GetVoltage() {return voltage;}

void CPMT::SetGain(double g) {gain=g;}
void CPMT::SetVoltage(double v) {voltage=v;}

Я скомпилировал это так:

g++ make_pmt.C pmt.C

Вам также необходимо добавить конструктор и инициализировать усиление и напряжение.

person Eddy Pronk    schedule 27.04.2010
comment
Я читал, что если вы не укажете конструктор, он автоматически использует конструктор по умолчанию без каких-либо аргументов. Ага? И я просто не инициализировал переменные, потому что это был просто поучительный пример. Или совершенно необходимо иметь что-то большее, чем double gain, voltage? - person physicsmichael; 27.04.2010
comment
Он сгенерирует конструктор по умолчанию, но коэффициент усиления и напряжение не будут инициализированы и будут содержать мусор. - person Eddy Pronk; 28.04.2010

Синтаксис вашего файла pmt.C неверен. Следует читать

double CPMT::GetGain() {return gain;}
double CPMT::GetVoltage() {return voltage;}
void CPMT::SetGain(double g) {gain=g;}
void CPMT::SetVoltage(double v) {voltage=v;}
person Gareth Stockwell    schedule 27.04.2010

В pmt.c вы переопределяете класс. Вместо этого вы должны просто определить его функции:

double CPMT::GetGain() { return gain; }
double CPMT::GetVoltage() {return voltage;}

void CPMT::SetGain(double g) {gain=g;}
void CPMT::SetVoltage(double v) {voltage=v;}

Кроме того, вам необходимо убедиться, что вы предоставляете компоновщику оба объектных файла.

person Alex Korban    schedule 27.04.2010
comment
CPMT :: должно быть перед каждым именем функции - person Draco Ater; 27.04.2010

Попробуйте переименовать файлы .cpp или .cxx, ваш компилятор может предположить, что .C означает, что это C, а не C ++, что, похоже, так, поскольку он не искажает имена.

person Xorlev    schedule 27.04.2010
comment
Спасибо за предложение, но я уже давно использую расширение .C, и, похоже, все в порядке. Однако это был не мой выбор! - person physicsmichael; 27.04.2010

Похоже, вы компилируете и связываете отдельные файлы .C, которые сами по себе не являются полными. Вам нужно сначала скомпилировать их, чтобы получить .o файл, а затем связать .o файлы, чтобы получить окончательный исполняемый файл. Все это можно сделать с помощью:

g++ make_pmt.C pmt.C

Также pmt.C должен содержать только определение функций, объявленных в файле заголовка.

person codaddict    schedule 27.04.2010