Наследование C++ в отдельных файлах с использованием #include и Inclusion Guards

Я новичок в Stack Overflow и изучаю C++, но все еще новичок. После завершения большого куска книги, которой я пользуюсь (которую можно считать устаревшей и/или плохой книгой), я решил усилить некоторые концепции, попробовав их самостоятельно, ссылаясь на книгу только в случае необходимости, но я кажутся застрявшими. Концепции, которые я пытаюсь решить, — это наследование, полиморфизм, абстрактные типы данных (ADT) и разделение кода для моих классов на файлы заголовков (.h) и файл C++ (.cpp). Заранее извините за стену текста, я просто хочу быть четким и конкретным, где мне нужно быть.

Итак, моя цель — создать простые классы форм, которые наследуются друг от друга, где это применимо. У меня есть четыре класса: myPoly, myRectangle, myTriangle и mySquare. myPoly, если я правильно понял эту концепцию, должен быть ADT, поскольку один из методов является чистой виртуальной функцией (метод области), поскольку создание объекта myPoly - это не то, что я хотел бы, чтобы пользователь моих классов делал. myRectangle и myTriangle оба являются производными от myPoly, а mySquare, в свою очередь, является производным от myRectangle. Я также включил свою тестовую программу, в которую планировал тестировать свои классы. Я использую Code::Blocks 10.05 и продолжаю получать следующую ошибку при сборке программы test.cpp:

undefined reference to 'myPoly::myPoly()'

Я получаю 42 подобные ошибки для методов класса myPoly. Это происходит, когда я пытаюсь создать файлы .cpp для myRectangle и myTriangle. Изучая проблемы, с которыми я столкнулся в этом маленьком проекте, я попытался выяснить, что что-то не так с моими охранниками включения или операторами #include, и что-то не включается должным образом или включается слишком много раз. Сначала я предоставлял файл .cpp для myPoly для myRectangle и myTriangle, но в нескольких местах прочитал, что включение файла .h для myPoly более эффективно и каким-то образом автоматически включает его .cpp. Если кто-то может дать некоторое представление об этом, это будет очень признательно. Я также кое-что помню о том, что использование кавычек в операторах включения отличается от использования угловых скобок. Ниже представлены все девять файлов, которые я сделал для своего маленького проекта. Большинство комментариев — это небольшие заметки или напоминания для меня.

myPoly.h

//Practice with inheritance, polymorphism, and Abstract Data Types
//header file for Polygon class

#ifndef MYPOLY_H
#define MYPOLY_H

class myPoly
{
    public:
        //constructor
        //const reference pass because the values w and h don't change and reference avoid the time it takes to copy large
        //  objects by value (if there were any)
        myPoly();
        myPoly(const float & w, const float & h);

        //destructor
        virtual ~myPoly();

        //accessors
        float getWidth();
        float getHeight();
        void setWidth(const float & w);
        void setHeight(const float & h);

        virtual float area() = 0;

    private:
        float width, height;
};

#endif

myPoly.cpp

//Practice with inheritance, polymorphism, and Abstract Data Types
//implementation file for myPoly class

#include "myPoly.h"

//constructor
myPoly::myPoly()
{
    setWidth(10);
    setHeight(10);
}

myPoly::myPoly(const float & w, const float & h)
{
    setWidth(w);
    setHeight(h);
}

//destructor
myPoly::~myPoly() {}

//accessors
float myPoly::getWidth() {return width;}
float myPoly::getHeight() {return height;}

void myPoly::setHeight(const float & w) {width = w;}
void myPoly::setWidth(const float & h) {height = h;}

//pure virtual functions have no implementation
//area() is handled in the header file

мойПрямоугольник.h

//Practice with inheritance, polymorphism, and Abstract Data Types
//declaration file for myRectangle class

#ifndef MYRECTANGLE_H
#define MYRECTANGLE_H

#include "myPoly.h"

class myRectangle : public myPoly
{
    public:
        //constructor
        myRectangle();
        myRectangle(const float & w, const float & h);

        //destructor
        ~myRectangle();

        //this doesn't need to be virtual since the derived class doesn't override this method
        float area();
};

#endif

myRectangle.cpp

//Practice with inheritance, polymorphism, and Abstract Data Types
//implementaion file for the myRectangle class

//get a vauge compiler/linker error if you have virtual methods that aren't implemented (even if it ends up being just
//  a 'stub' method, aka empty, like the destructor)

#include "myRectangle.h"

myRectangle::myRectangle()
{
    setWidth(10);
    setHeight(10);
}

myRectangle::myRectangle(const float & w, const float & h)
{
    setWidth(w);
    setHeight(h);
}

myRectangle::~myRectangle()
{
}

float myRectangle::area()
{
    return getWidth() * getHeight();
}

мойТреугольник.h

//Practice with inheritance, polymorphism, and Abstract Data Types
//declaration file for myTriangle class

#ifndef MYTRIANGLE_H
#define MYTRIANGLE_H

#include "myPoly.h"

//imagine the triangle is a right triangle with a width and a height
//  |\
//  | \
//  |  \
//  |___\

class myTriangle : public myPoly
{
    public:
        //constructors
        myTriangle();
        myTriangle(const float & w, const float & h);

        //destructor
        ~myTriangle();

        //since nothing derives from this class it doesn't need to be virtual and in turn neither does the destructor
        float area();
};

#endif

myTriangle.cpp

//Practice with inheritance, polymorphism, and Abstract Data Types
//implementation file for myTriangle class

#include "myTriangle.h"

myTriangle::myTriangle()
{
    setWidth(10);
    setHeight(10);
}

myTriangle::myTriangle(const float & w, const float & h)
{
    setWidth(w);
    setHeight(h);
}

myTriangle::~myTriangle()
{
}

float myTriangle::area()
{
    return getWidth() * getHeight() / 2;
}

mySquare.h

//Practice with inheritance, polymorphism, and Abstract Data Types
//declaration file for mySquare class

#ifndef MYSQUARE_H
#define MYSQUARE_H

#include "myRectangle.cpp"

class mySquare : public myRectangle
{
    public:
        //constructors
        mySquare();
        //explicity call the myRectangle constructor within this implementation to pass w as width and height
        mySquare(const float w);

        //destructor
        ~mySquare();
};

#endif

mySquare.cpp

//Practice with inheritance, polymorphism, and Abstract Data Types
//implementation file for mySquare class

#include "mySquare.h"

mySquare::mySquare()
{
    setWidth(10);
    setHeight(10);
}

mySquare::mySquare(const float w)
{
    myRectangle::myRectangle(w, w);
}

mySquare::~mySquare()
{
}

test.cpp

//Practice with inheritance, polymorphism, and Abstract Data Types
//main class that uses my shape classes and experiments with inheritance, polymorphism, and ADTs

#include "myRectangle.cpp"
//#include "mySquare.cpp"
#include "myTriangle.cpp"

#include <iostream>

int main()
{
    myPoly * shape = new myRectangle(20,20);

    return 0;
}

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


person RIBdot    schedule 31.07.2012    source источник
comment
При быстром просмотре я заметил, что MySquare.h должно включать MyRectangle.h, а не MyRectangle.cpp.   -  person StarPilot    schedule 01.08.2012
comment
Спасибо, что поймали это. Я обязательно изменю это в своем коде.   -  person RIBdot    schedule 01.08.2012


Ответы (4)


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

Однако в вашем коде есть одна «проблема». Как правило, следует использовать только файлы #include .h, а не файлы .cpp.

Теперь перейдем к решению: я сам не знаком с Code::Blocks. Тем не менее, я надеюсь, что смогу дать некоторую общую информацию, которая укажет вам правильное направление. Некоторые компиляторы, которые я использовал в прошлом, по умолчанию позволяли мне скомпилировать один файл C++ и запустить программу. Чтобы скомпилировать программу с более чем одним файлом, мне пришлось создать проект. (Большинство современных компиляторов вынуждают вас создавать проект с самого начала.) Имея это в виду, я предлагаю вам проверить, как создать проект для вашей программы в Code::Blocks.

person Code-Apprentice    schedule 31.07.2012
comment
Спасибо за отзыв. Я переместил все свои файлы в проект Code::Blocks и повторно скомпилировал файл test.cpp, и ошибки с неопределенными ссылками исчезли. Я знаю, вы сказали, что не знакомы с Code::Blocks, но знаете ли вы, что по-другому предварительно сформировано, что добавление этих файлов в проект решило эту ошибку? Предоставляет ли проект компоновщику или компилятору некоторую информацию, которой не было в моих файлах, пока они были сами по себе? - person RIBdot; 01.08.2012
comment
Я немного покопался, и кто-то поправит меня, если я ошибаюсь, но если я правильно понял, что я прочитал, я думаю, что когда я пытался собрать только test.cpp, до того, как все мои файлы были в одном проекте, команда сборки только скомпилировала тест. cpp и не связывал файл .o с другими файлами .o. Перемещая все мои файлы в проект, я считаю, что проект выполняет правильную команду компоновщика, чтобы скомпилировать все девять моих файлов и соответствующим образом связать все их файлы .o, аналогично тому, что второй пункт w00te предложил несколько ответов. - person RIBdot; 01.08.2012
comment
@RIBdot Я не мог бы объяснить это лучше, чем ты. Так, скорее всего, и произошло при создании проекта. Хотя я не знаком конкретно с Code::Blocks, это то, что делают все компиляторы, с которыми я знаком. - person Code-Apprentice; 02.08.2012

С точки зрения кода (по крайней мере, то, что я просмотрел), это выглядит довольно хорошо, но:

Есть две вещи, которые следует учитывать:

  1. Не включайте файлы cpp напрямую. Например, в mySquare.h #include "myRectangle.cpp" должно быть #include "myRectangle.h". Вы хотите включить интерфейс/объявления, представленные в заголовочном файле, которые сообщают программе, как создать класс, а не только определения функций.

  2. Во-вторых, убедитесь, что вы компилируете все свои объектные файлы. Я не знаю блоков кода, но если бы вы использовали g++ или что-то в этом роде, вы бы хотели сделать g++ main.cpp myPoly.cpp mySquare.cpp etc. для всех файлов. Подобная ошибка может произойти, если вы забудете myPoly.cpp, например, потому что не будут включены определения его функций.

person John Humphreys    schedule 31.07.2012
comment
Спасибо за ваш вклад. Должен ли мой файл test.cpp включать только файлы заголовков, а не файлы .cpp? - person RIBdot; 01.08.2012
comment
Я думаю, что на самом деле ответил на свой вопрос. Я последовал совету, оставленному Code Guru несколькими ответами ниже, и перенес все свои классы в проект. Это оставило меня с множественной ошибкой определения для класса myRectangle. Итак, я обратился к своему файлу test.cpp и начал отслеживать, где myRectangle включался более одного раза, и вот, изменение #include myRectnalge.cpp на #include myRectangle.h устранило множественные ошибки определения. - person RIBdot; 01.08.2012

На самом деле все выглядит нормально. Вероятно, это так же просто, как не включать myPoly.obj при компоновке вашей программы. Я не знаком с Code::Blocks (хотя я знаю, что он довольно популярен), но я предполагаю, что если вы просто, например, нажмете на test.cpp и выберите «Выполнить», то Code::Blocks попытается собрать программу из только один исходный файл. Вам нужно будет включить все соответствующие исходные файлы в каждую программу, которую вы создаете.

person Ernest Friedman-Hill    schedule 31.07.2012
comment
Спасибо за ответ. Я считаю, что правильная команда компоновщика была выполнена теперь, когда я переместил все свои файлы в один проект в Code:: Blocks, как предложил Code-Guru. - person RIBdot; 01.08.2012

В дополнение к тому, что сказали другие ребята: вы неправильно делаете наследование...

Когда вы делаете

class Poly
{
   Poly();
   ~Poly();
}

class Rect : public Poly()
{
   Rect();
   ~Rect();
}

Вам нужно объявить дочерний конструктор следующим образом:

Rect::Rect() : Poly()
{

}

Ребенок должен быть построен только после того, как отец закончил строительство.

person gibertoni    schedule 31.07.2012
comment
Ну нет. Если вы не вызываете конструктор суперкласса явно, конструктор по умолчанию вызывается автоматически. Ваши изменения добавляют беспорядок и вообще не меняют сгенерированный код. - person Ernest Friedman-Hill; 01.08.2012
comment
На самом деле, если вы не указываете явный вызов родительского конструктора, код становится неоднозначным. Скорее всего, программист забудет тот факт, что вызывается конструктор родителей по умолчанию и что там могут происходить или не происходить некоторые вещи (инициализация и т. д.). Отладка становится только сложнее в будущем. - person gibertoni; 01.08.2012