Какие плохие вещи произойдут, если вы напишете весь класс в одном файле на С++?

В С# или Java классы объявляются и определяются одновременно. В С++ норма должна делать это отдельно. Что, если мы напишем весь класс в одном, скажем .cpp, файле и включим в файлы, ссылающиеся на него, какие бы плохие вещи технически не произойдут, кроме удлиненного процесса компиляции?

Ответ 1

Если ваша реализация MyClass находится в файле заголовка MyClass.h, тогда любой файл, который вам нужен для реализации MyClass, будет включен, когда кто-то включает MyClass.h.

Если вы меняете какую-либо часть MyClass.h, даже если она тривиальна (например, добавление комментария или даже пробела), то все файлы, которые ее включают, придется перекомпилировать, даже если интерфейс не изменился.

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

Например, если у меня есть следующее:

// MyClass.h
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>

#include "Inventory.h"

class MyClass
{
public:
  MyClass();

  void processInventory(Inventory& inventory)
  {
    // Do something with each item in the inventory here
    // that uses iostream, iomanip, sstream, and string
  }
private:
  // ...
};

Это было бы более идеоматически написано как:

// MyClass.h
class Inventory;

class MyClass
{
public:
  MyClass();

  void processInventory(Inventory& inventory);
private:
  // ...
};

// MyClass.cc
#include "MyClass.h"

#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>

#include "Inventory.h"

MyClass()::MyClass()
{
}

void MyClass()::processInventory(Inventory& inventory)
{
  // Do something with each item in the inventory here
  // that uses iostream, iomanip, sstream, and string
}

Примечание: включение MyClass.h не означает iostream, iomanip, sstream, string или Inventory.h должно быть проанализировано. Изменение работы processInventory не означает, что все файлы с использованием MyClass.h должны быть перекомпилированы.

Обратите внимание, насколько проще это выяснить, как использовать MyClass сейчас. Файлы заголовков служат важной цели: они показывают людям, как использовать ваш класс. С измененным MyClass.h легко увидеть список функций. Если каждая функция определена в заголовке, вы не можете смотреть только на список функций. Это затрудняет определение того, как использовать класс.

Ответ 2

Вы можете нарушить одно правило определения.

Если вы пишете это:

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

foo::doit() {}

и включите, что в нескольких классах вы будете иметь несколько определений foo::doit, и ваша ссылка не удастся.

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

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

или явно сделав их inline:

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

inline void foo::doit() {}

то вы можете включить этот файл столько раз, сколько хотите.

Ответ 3

Компонент увидит несколько определений членов класса при попытке объединить несколько таких объектов. Таким образом, вы не сможете создавать двоичные файлы из исходных файлов, которые содержат что-либо в нескольких местах.

Ответ 4

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

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

Каждый .cpp, включенный в класс, будет компилироваться в объектные файлы. Однако каждый класс должен иметь только 1 общее определение, иначе вы не сможете связать свои объектные файлы вместе.

Ответ 5

Самое главное, чтобы понять, что #include отличается от других импортирующих методов, заключается в том, что #include COPIES содержимое этого файла, где размещена директива #include. Таким образом, объявление и определение класса в том же файле создаст три вещи:

  • Значительно увеличить ваш компилятор
    раз.

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

  • Это продемонстрирует реализацию пользователю, а не только интерфейсу.

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

Ответ 6

@Bill

Я думаю, что важно подчеркнуть точку Билла:

Обратите внимание, насколько проще это может быть выясните, как использовать MyClass сейчас. Файлы заголовков служат важным Цель: они показывают людям, как использовать ваш класс.

.h файл является более или менее "общедоступным" документом, позволяющим понять, как ваш класс работает в некотором смысле концептуально - интерфейс. Помните, что исходный файл должен считаться патентованным. Я помню, как много узнал о том, как Unix работал в мои ранние дни C/С++, читая файлы заголовков. Также помните, что сложность встроенных функций должна быть не более чем доступной

Ответ 7

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

Ответ 8

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