Почему нельзя использовать прямое объявление для std :: vector?

Если я создам такой класс:

// 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 в этом вопросе не очень информативны.

Обратите внимание, что вы может передаете vector<T> в функцию только с объявленным вперед типом T, если вы передаете его как vector<T>& (но не vector<T>, потому что для этого потребуется операция копирования)

bobobobo 26.07.2013 22:01
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
30
1
16 722
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Чтобы создать экземпляр A :: v, компилятор должен знать конкретный тип B.

Если вы пытаетесь минимизировать количество #included багажа, чтобы улучшить время компиляции, вы можете сделать две вещи, которые на самом деле являются вариациями друг друга:

  1. Используйте указатель на B
  2. Используйте легкий доверенное лицо к B
Ответ принят как подходящий

Компилятору необходимо знать размер «B», прежде чем он сможет сгенерировать соответствующую информацию о макете. Если вместо этого вы укажете std::vector<B*>, то компилятору не нужно будет знать размер B, потому что он знает размер указателя.

Но насколько я знаю, std :: vectorv должен содержать только указатель на B *, который должен иметь тот же эффект, что и определение: B * здесь

baye 25.03.2011 16:42

Это не просто указатель на B - как минимум, необходимо создать массив B для хранения в этом указателе. А для создания массива нужно знать размер B.

Curt Hagenlocher 25.04.2011 05:52

@lzprgmr: Действительно, vector<T>, вероятно, содержит только указатель на T, поэтому я не согласен с ответом Курта. Макет vector<B> может быть известен, не зная определения B, но поскольку vector является шаблоном, все его функции-члены должны быть созданы для каждого аргумента шаблона: вы не можете отделить их объявления от их реализаций. Поскольку для некоторых из этих функций-членов потребуется определение T, для использования в vector он должен быть полным типом. Проблема связана с тем, что функции-члены определены встроенными, а не в макете vector<B> в зависимости от макета B.

Luc Touraille 18.11.2011 18:30

@LucTouraille: Подожди, разве это не совсем неправильный ответ? Как говорят другие, проблема здесь во встроенном конструкторе, а не в контейнере как таковом ...

Nemo 22.05.2015 19:22

@Nemo: Да, я думаю, это неверно. Вполне возможно реализовать контейнер способом, не требующим полного типа, на самом деле многие контейнеры Boost могут быть без проблем созданы с неполным типом. Контейнеры стандартной библиотеки требуют полного типа, потому что так говорится в стандарте, а в стандарте это сказано в основном по историческим причинам, как объяснено здесь.

Luc Touraille 23.05.2015 17:13

Причина, по которой вы не можете использовать форвардное объявление, заключается в том, что размер B неизвестен.

В вашем примере нет причин, по которым вы не можете включить B.h в A.h, так какую проблему вы действительно пытаетесь решить?

Редактировать: Есть и другой способ решить эту проблему: прекратить использование C / C++! Это такие 1970-е ...;)

«прекратить использование C / C++» не кажется решением исходной проблемы. пациент: «доктор, мне больно, когда я это делаю», врач: «ну, тогда не делай этого!» Есть ли другой язык или инструмент, который может хорошо взаимодействовать с C++, который вы бы предложили взамен?

Aaron 17.09.2008 21:31

Это больше, чем просто размер 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.

Ky Waegel 24.09.2016 01:54

Просто чтобы добавить дополнительную информацию к делу ... Visual Studio VS2017 теперь работает как gcc и отлично компилируется.

Hopobcn 30.07.2018 12:25

Как и сказал fyzix, причина, по которой ваше предварительное объявление не работает, связана с вашим встроенным конструктором. Даже пустой конструктор может содержать много кода, например создание элементов, не являющихся членами POD. В вашем случае у вас есть вектор для инициализации, чего вы не можете сделать, не определив полностью его тип шаблона.

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

Чтобы избавиться от этой проблемы, просто не используйте встроенные конструкторы и деструкторы. Определите их отдельно где-нибудь после того, как B будет полностью определен.

Для дополнительной информации, http://www.chromium.org/developers/coding-style/cpp-dos-and-donts

Другие вопросы по теме