Взаимозависимые классы С++, поддерживаемые std::vector
Мне было любопытно, было ли возможно создать два класса, каждый из которых содержит std::vector
другого. Мое первое предположение заключалось в том, что это было бы невозможно, потому что std::vector
требует полного типа, а не только прямого объявления.
#include <vector>
class B;
class A { std::vector<B> b; };
class B { std::vector<A> a; };
Я бы подумал, что объявление std::vector<B>
приведет к немедленному сбою, потому что B
имеет неполный тип в этой точке. Тем не менее, он успешно компилируется под gcc и clang без каких-либо предупреждений. Почему это не вызывает ошибку?
Ответы
Ответ 1
T.C прокомментировал, что это фактически поведение undefined, которое рассматривается в запросе на изменение стандарта. Нарушение правила, по-видимому, [res.on.функции] 2.5:
В частности, эффекты undefined в следующих случаях: [...]
- если неполный тип (3.9) используется в качестве аргумента шаблона при создании экземпляра компонента шаблона, если это специально не разрешено для этого компонента.
Причина, по которой это работает (и причина, по которой она может быть стандартизирована), двояка:
-
Ваша программа содержит только определения типов; объекты не создаются, вызовы не вызываются, код не генерируется. Если мы упростим это, исключив определение B (он не нужен), а затем попытайтесь создать экземпляр A
, он не сработает:
class B;
class A { std::vector<B> b; };
A a; // error: ctor and dtor undefined for incomplete type B.
То, что также терпит неудачу, как и ожидалось, является простым
std::vector<B> b;
Причина в обоих случаях заключается в том, что компилятор должен создавать код, в отличие от простого объявления типа, которое является только грамматически релевантным.
Использование векторного типа также хорошо в других контекстах:
typedef std::vector<B> BVec;
-
Класс A
можно определить, потому что, как правильно говорит Николай в своем ответе, размер std::vector<B>
и, следовательно, размер члена A
b
не зависит от определения b
(потому что вектор содержит указатель на массив элементов, а не собственный массив).
Ответ 2
Это компилируется и работает нормально, поскольку std::vector
использует указатели, а не точное определение A
или B
. Вы можете изменить свой пример, чтобы использовать один экземпляр или массив в определении классов, подобных этому.
class B;
class A { public: B b[2]; };
class B { public: A a[2]; };
Это, очевидно, не скомпилируется, поскольку вы пытаетесь использовать определение классов. И ошибка компилятора будет такой, какой вы ожидаете
ошибка: поле 'b имеет неполный тип' B [2]
Однако
class B;
class A { public: B* b; };
class B { public: A* a; };
будет работать как std::vector
. Таким образом, вам не нужен полностью определенный класс для использования указателя или ссылки на этот тип.
Также есть упрощенный пример с шаблонами
template<typename T>
struct C {
T* t;
};
class B;
class A { public: C<B> b; };
class B { public: C<A> a; };
Это также будет работать, и, конечно же, вы можете его создать
int main() {
B b;
A a;
}
Ответ 3
Это из-за правил экземпляра шаблона. В точке объявления A
требуется создать интерфейс std::vector<B>
.
Так как интерфейс std::vector
использует только указатели и ссылки на свой тип значения, а специализация в вашем примере получает экземпляр, но ни одна из его функций не используется, вы не получаете для него ошибку компилятора.
Что касается того, почему компилятор не сгенерировал A::A()
— это вызвало бы ошибку, вызвав конструкцию std::vector<B>
constructor — потому что ваш контекст требовал только своего объявления. Компилятор должен только генерировать значения по умолчанию для этих специальных функций-членов, если видит, что они используются.
Ссылки
& раздел; 14.7.1
[...] Неявное инстанцирование специализации шаблона шаблона вызывает неявное создание объявлений, но не определений или аргументов по умолчанию, функций-членов класса, классов-членов, облачных имен элементов, статических элементов данных и Шаблоны участников [.]
& раздел; 12
[1] [...] Реализация будет неявно объявлять эти [специальные] функции-члены для некоторых типов классов, когда программа явно не объявляет их. Реализация будет неявно определять их, если они используются odr.