Если я создам такой класс:
// B.h
#ifndef _B_H_
#define _B_H_
class B
{
private:
int x;
int y;
};
#endif // _B_H_
и используйте это так:
// main.cpp
#include <iostream>
#include <vector>
class B; // Forward declaration.
class A
{
public:
A() {
std::cout << v.size() << std::endl;
}
private:
std::vector<B> v;
};
int main()
{
A a;
}
Компилятор не работает при компиляции main.cpp. Теперь я знаю решение для #include "B.h", но мне любопытно, почему он не работает. Сообщения об ошибках g++ или cl в этом вопросе не очень информативны.





Чтобы создать экземпляр A :: v, компилятор должен знать конкретный тип B.
Если вы пытаетесь минимизировать количество #included багажа, чтобы улучшить время компиляции, вы можете сделать две вещи, которые на самом деле являются вариациями друг друга:
Компилятору необходимо знать размер «B», прежде чем он сможет сгенерировать соответствующую информацию о макете. Если вместо этого вы укажете std::vector<B*>, то компилятору не нужно будет знать размер B, потому что он знает размер указателя.
Но насколько я знаю, std :: vectorv должен содержать только указатель на B *, который должен иметь тот же эффект, что и определение: B * здесь
Это не просто указатель на B - как минимум, необходимо создать массив B для хранения в этом указателе. А для создания массива нужно знать размер B.
@lzprgmr: Действительно, vector<T>, вероятно, содержит только указатель на T, поэтому я не согласен с ответом Курта. Макет vector<B> может быть известен, не зная определения B, но поскольку vector является шаблоном, все его функции-члены должны быть созданы для каждого аргумента шаблона: вы не можете отделить их объявления от их реализаций. Поскольку для некоторых из этих функций-членов потребуется определение T, для использования в vector он должен быть полным типом. Проблема связана с тем, что функции-члены определены встроенными, а не в макете vector<B> в зависимости от макета B.
@LucTouraille: Подожди, разве это не совсем неправильный ответ? Как говорят другие, проблема здесь во встроенном конструкторе, а не в контейнере как таковом ...
@Nemo: Да, я думаю, это неверно. Вполне возможно реализовать контейнер способом, не требующим полного типа, на самом деле многие контейнеры Boost могут быть без проблем созданы с неполным типом. Контейнеры стандартной библиотеки требуют полного типа, потому что так говорится в стандарте, а в стандарте это сказано в основном по историческим причинам, как объяснено здесь.
Причина, по которой вы не можете использовать форвардное объявление, заключается в том, что размер B неизвестен.
В вашем примере нет причин, по которым вы не можете включить B.h в A.h, так какую проблему вы действительно пытаетесь решить?
Редактировать: Есть и другой способ решить эту проблему: прекратить использование C / C++! Это такие 1970-е ...;)
«прекратить использование C / C++» не кажется решением исходной проблемы. пациент: «доктор, мне больно, когда я это делаю», врач: «ну, тогда не делай этого!» Есть ли другой язык или инструмент, который может хорошо взаимодействовать с C++, который вы бы предложили взамен?
Это больше, чем просто размер B. В современных компиляторах есть хитрые приемы для ускорения векторных копий с помощью, например, memcpy там, где это возможно. Обычно это достигается частичной специализацией на POD-типе элемента. Вы не можете сказать, является ли B POD из форвардного объявления.
Неважно, используете ли вы вектор или просто пытаетесь создать экземпляр B. Для создания экземпляра требуется полное определение объекта.
Чувак, вы создаете экземпляр std::vector с неполным типом. Не трогайте предварительное объявление, просто переместите определение конструктора в файл .cpp.
Фактически, ваш пример был бы построен, если бы конструктор A был реализован в модуле компиляции, который знает тип B.
Экземпляр std :: vector имеет фиксированный размер, независимо от того, что такое T, поскольку он содержит, как говорили ранее, только указатель на T. Но конструктор вектора зависит от конкретного типа. Ваш пример не компилируется, потому что A () пытается вызвать вектор ctor, который не может быть сгенерирован, не зная B. Вот что будет работать:
Заявление А:
// A.h
#include <vector>
class B; // Forward declaration.
class A
{
public:
A(); // only declare, don't implement here
private:
std::vector<B> v;
};
Реализация A:
// A.cpp
#include "A.h"
#include "B.h"
A::A() // this implicitly calls vector<B>'s constructor
{
std::cout << v.size() << std::endl;
}
Теперь пользователю A нужно знать только A, а не B:
// main.cpp
#include "A.h"
int main()
{
A a; // compiles OK
}
Обратите внимание, что это работает с GCC и Clang, но не с Visual Studio. VS2015 выходит из строя с ошибкой error C2036: 'B *': unknown size. Причина не совсем ясна, но возможно, что VS2015 выполняет более раннее создание экземпляра шаблона vector<T>, чем GCC / Clang.
Просто чтобы добавить дополнительную информацию к делу ... Visual Studio VS2017 теперь работает как gcc и отлично компилируется.
Как и сказал fyzix, причина, по которой ваше предварительное объявление не работает, связана с вашим встроенным конструктором. Даже пустой конструктор может содержать много кода, например создание элементов, не являющихся членами POD. В вашем случае у вас есть вектор для инициализации, чего вы не можете сделать, не определив полностью его тип шаблона.
То же самое и с деструкторами. Вектор нуждается в определении типа шаблона, чтобы указать, какой деструктор вызывать при уничтожении содержащихся в нем экземпляров.
Чтобы избавиться от этой проблемы, просто не используйте встроенные конструкторы и деструкторы. Определите их отдельно где-нибудь после того, как B будет полностью определен.
Для дополнительной информации, http://www.chromium.org/developers/coding-style/cpp-dos-and-donts
Обратите внимание, что вы может передаете
vector<T>в функцию только с объявленным вперед типомT, если вы передаете его какvector<T>&(но неvector<T>, потому что для этого потребуется операция копирования)