LINUX.ORG.RU

[3 часа ночи][C++] что не так в системе хедеров?


0

0

main.cpp:

#include "a.h"
#include "b.h"

int main() {}

a.h:

#ifndef A_H
#define A_H

#include "b.h"

struct A {
    bool someFlag;
    A (B inB);
};

#endif

b.h:

#ifndef B_H
#define B_H

#include "a.h"

struct B {
    bool someFlag;
    void SomeFunct (A inA);
};

#endif

a.cpp:

#include "a.h"

A::A(B inB) {
  flag=!inB.flag;
}

b.cpp:

#include "b.h"

void B::SomeFunct(A inA) {
  flag=inA.flag;
}

Попытка скомпилить:

% g++ ./main.cpp
In file included from ./a.h:4,
                 from ./main.cpp:3:
./b.h:8: ошибка: ‘A’ has not been declared

Что не так с инклудами и хедерами?

★★★★★

Кстати, для софтины, которая будет использовать Qt и разрабатывается для Linux, MacOS X, FreeBSD, Windows стоит использовать #pragma once, или лучше таки #include guards?

Obey-Kun ★★★★★ ()

пиши так:

class B;

class A
{
...
   void SomeFunct( B value );
...
};

вообще включать одни хедера из других - это плохо и стоит этого избегать

lester ★★★★ ()
Ответ на: комментарий от Obey-Kun

> стоит использовать #pragma once, или лучше таки #include guards?

я пользуюсь #pragma once - никаких проблем на разных ОС и компиляторах не было

lester ★★★★ ()
Ответ на: комментарий от lester

>вообще включать одни хедера из других - это плохо и стоит этого избегать

Практически все проекты с тобой не согласятся: boost, qt, kernel-headers — везде присутствует #include в *.h файлах. Просто надо избегать циклических зависимостей.

linuxfan ()
Ответ на: комментарий от linuxfan

> boost

qt


спасибо за хороший пример моей правоты :) чем такое плохо - так это впервую очередь замедлением скорости компиляции

lester ★★★★ ()
Ответ на: комментарий от Obey-Kun

а как быть, если у меня в определении класса A используются переменные класса B (и наоборот)?

использовать ссылочные типы и forward declarations

jtootf ★★★★★ ()
Ответ на: комментарий от jtootf

не понял, при чём здесь ссылочные типы (и каким боком они относятся к коду в сабже), но насчёт forward declarations всё ясно

Obey-Kun ★★★★★ ()
Ответ на: комментарий от jtootf

при том, что иначе forward declaraion у тебя хрен скомпилируется

пример выше:

class B;
class A 
{ 
void SomeFunct( B value );
};

прекрасно скомпилируется ;)

lester ★★★★ ()
Ответ на: комментарий от lester

а не скомпилится по ссылке из-за:

private:
A fA ;

и тут все очевидно

lester ★★★★ ()
Ответ на: комментарий от jtootf

> может ещё объяснишь, чем в этом примере B value лучше, чем B const & value?

хуже конечно - я просто сохранил «авторский код»

lester ★★★★ ()
Ответ на: комментарий от lester

я просто к тому, что в контексте фигурируют две вещи, которые стоило бы делать по умолчанию: ссылочные типы и forward declarations; связь между ними - в примере по ссылке

к таким же действиям по умолчанию стоило бы отнести использование explicit для одноаргументных конструкторов, втыкание const во все места, кроме тех, где он явно не нужен; преинкремент. впрочем, это уже замечания, для которых необходимо минимум капитанское звание

jtootf ★★★★★ ()
Ответ на: комментарий от jtootf

> впрочем, это уже замечания, для которых необходимо минимум капитанское звание

ну на самом деле это все несложно - просто многие ленятся читать книги и доходят до этого сами, ес-но теряя кучу времени - своего и/или чужого, как тот же ТС - который судя по всему за книги так и не взялся :)

lester ★★★★ ()

Ну как - a.h подключает b.h, тот в самом начале снова подключает a.h, но, так как переменная A_H уже определена, тот на самом деле не подключается. Поэтому, в момент, когда определяется класс B, класс A ещё не определён. Ублюдочная сишная система, требующая задекларировать всё раньше, чем оно используется.

Miguel ★★★★★ ()
Ответ на: комментарий от Miguel

Ублюдочная сишная система, требующая задекларировать всё раньше, чем оно используется.

пусть у нас есть такой код:

struct B;
struct A{ B f1; int f2 };
static_assert( offsetof( A, f2 ) == 4 );

а теперь расскажи, какую надо подсунуть компилятору версию libastral, чтоб скомпилировать этот файл

lester ★★★★ ()
Ответ на: комментарий от lester

>чем такое плохо - так это впервую очередь замедлением скорости компиляции

Чем быстрее программа собирается тем она качественее, да?

AF ★★★ ()
Ответ на: комментарий от AF

> Чем быстрее программа собирается тем она качественее, да?

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

lester ★★★★ ()
Ответ на: комментарий от lester

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

Большие проекты какбы принято разделять на модули, слои, компоненты и все такое ;) Если в результате изменения хедера надо персобрать 1-2 модуля, то ничего страшного не произойдет. Это не такое уж большое время. Если надо пересобрать большу часть проекта, то проект неправильно разбит на модули.

ЗЫ: Подход успешно проверен на проекте в 1,5 млн строк кода.

ЗЗЫ: ниче против forward declaration вместо инклюда не имею, и сам им пользуюсь, но без фанатизма.

AF ★★★ ()
Ответ на: комментарий от AF

> Большие проекты какбы принято разделять на модули, слои, компоненты и все такое ;)

попробуй поменять что-либо в qfont.h, qwidget.h и т.п. - и увидишь как много на них завязано ;)

Если в результате изменения хедера надо персобрать 1-2 модуля, то ничего страшного не произойдет. Это не такое уж большое время


какое именно?

Если надо пересобрать большу часть проекта, то проект неправильно разбит на модули.


(немного перекручу) т.е. где-то торчит левый #include ;)

сам им пользуюсь, но без фанатизма.


ну так и я не стараюсь любой кровью не допустить лишнего #include

lester ★★★★ ()
Ответ на: комментарий от lester

>попробуй поменять что-либо в qfont.h, qwidget.h и т.п. - и увидишь как много на них завязано ;)

Ну это смотря как изменять. В qt таки есть бинарная совместимоть, так шо возможен вариант пересборки только QtGui. Хотя тут ты прав, и даже за это время можно в чае утопится. Радует только то, что в такие основополагающие хедера не часто приходится лазить.

Если в результате изменения хедера надо персобрать 1-2 модуля, то ничего страшного не произойдет. Это не такое уж большое время

какое именно?

У меня было до 2 минут максимум. В большинстве случаев до минуты.

(немного перекручу) т.е. где-то торчит левый #include ;)

Если бы было так, то я, наверное, был бы счастлив )

На практике фантазия быдлокодеров безгранична. Помнится был у меня проект - система сборки полсе малейшего изменения почти 2 минуты парсила свои jam файлы для поиска зависимостей.

AF ★★★ ()
Ответ на: комментарий от lester

>ну так и я не стараюсь любой кровью не допустить лишнего #include

После

никогда не подключай хедеры из хедеров. никогда

Это какбы не очевидно

AF ★★★ ()
Ответ на: комментарий от lester

Таки да, посыпаю голову пеплом

От пингвинов в глазах рябит, фиг поймешь, кто есть кто.

AF ★★★ ()
Ответ на: комментарий от lester

а теперь расскажи, какую надо подсунуть компилятору версию libastral, чтоб скомпилировать этот файл

Никакую.

А вот

struct A {
  B f1;
  int f2;
};
static_assert(offsetof(A, f2) == 4);
struct B {
  int f3;
};
мог бы и скомпилить, для этого атсрал не нужен.

Miguel ★★★★★ ()
Ответ на: комментарий от lester

>чем такое плохо - так это впервую очередь замедлением скорости компиляции

Это побочный эффект от шаблонов. А так предстваь себе квадратные глаза разработчика, когда он иклюдит хидер твоей библиотеки, а в ответ ему портянка из «не знаю, что такое int32_t/time_t».

linuxfan ()
Ответ на: комментарий от lester

> я пользуюсь #pragma once

с точки зрения педанта это неправильно. Нужно использовать include guard + опционально #pragma once, но не один #pragma once.

Потому что #pragma это такая штука которая должна обладать таким свойством — если ее убрать, то код остается корректным — возможно менее эффективным, но корректным. Использование одних #pragme once не обладает таким свойством.

dilmah ★★★★★ ()
Ответ на: комментарий от jtootf

я тут запутался немного

есть struct S, class C1 и class C2.

class C1 в хедере выглядит так:

class C1 {
  S something;
};

сlass C2 в его хедере выглядит схоже:

class C2 {
  S something1;
  S something2;
};

имеется c1.h, c1.cpp, c2.h, c2.cpp. Куда запихнуть struct S и то делать с инклюдами?

Obey-Kun ★★★★★ ()
Ответ на: комментарий от Obey-Kun

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

Booster ★★ ()
Ответ на: комментарий от Booster

лучше избегать этого, для того чтобы скорость компиляции была быстрее

ну твою ж дивизию, а, а cross references - это фантастика?

jtootf ★★★★★ ()
Ответ на: комментарий от Booster

Динамические объекты ещё нужно создать, правильно уничтожить.

о ужас. потому давайте все дружно забивать на хорошие практики программирования на C++, ведь работа с динамическими объектами - это так сложно

jtootf ★★★★★ ()
Ответ на: комментарий от jtootf

Зачем усложнять логику, ради эфемерных преимуществ компиляции?

Booster ★★ ()
Ответ на: комментарий от jtootf

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

Booster ★★ ()
Ответ на: комментарий от Booster

плюс каждый дополнительный уровень косвенности это дополнительный оверхед, неприемлемый для мелких вещей.

dilmah ★★★★★ ()
Ответ на: комментарий от lester

Плюсую. А инкладить нужно только при наследовании. Т.е. когда наследуемый класс находится в другом сырце.

tia ()
Ответ на: комментарий от jtootf

Блин, мозг всё взрывается.

#include <iostream>
using namespace std;

struct Phased {
    double melted;
    double frozen;
    Phased (double inMelted, double inFrozen)
    {
	melted=inMelted;
	frozen=inFrozen;
    }
};

class TestClass {
    Phased phasedThing;
public:
    TestClass (Phased *inPhased) { phasedThing = *inPhased; }
    void DamnFunct () { cout << phasedThing.frozen << endl; }
};

int main() {
    Phased myPhased(1,2);
    TestClass myTestClass(myPhased);
    myTestClass.DamnFunct();
}

g++ ругается:

./test.cpp: In constructor ‘TestClass::TestClass(Phased*)’:
./test.cpp:17: ошибка: нет подходящей функции для вызова ‘Phased::Phased()’
./test.cpp:7: замечание: претенденты: Phased::Phased(double, double)
./test.cpp:4: замечание:              Phased::Phased(const Phased&)
./test.cpp: In function ‘int main()’:
./test.cpp:23: ошибка: нет подходящей функции для вызова ‘TestClass::TestClass(Phased&)’
./test.cpp:17: замечание: претенденты: TestClass::TestClass(Phased*)
./test.cpp:14: замечание:              TestClass::TestClass(const TestClass&)
};

что, блин, не так? :(

Obey-Kun ★★★★★ ()
Ответ на: комментарий от Booster

Указатели используется не для того, чтобы чуток ускорить компиляцию.

ещё раз - что ты предлагаешь делать с cross-ref'ами? мне плевать на скорость компиляции, меня этот момент интересует

jtootf ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.