Подтвердить что ты не робот

В чем опасность форвардных деклараций?

У меня только что было интервью. Меня спросили, что такое "форвардная декларация". Затем меня спросили, являются ли они опасностями, связанными с передовыми объявлениями.

Я не мог ответить на второй вопрос. Поиск в сети не показал никакого интересного результата.

Итак, кто-нибудь знает какие-либо опасности, связанные с использованием форвардных объявлений?

4b9b3361

Ответ 1

Вперед декларация является <сильным > симптомом недостающих модулей С++ (будет исправлено в С++ 17?) и с использованием включения заголовков, если у С++ были модули, не было необходимости вообще для форвардных объявлений.

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

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

Ответ 2

Ну, кроме вопросов о дублировании...

... в стандарте есть по крайней мере одно заболевание.

Если вы вызываете delete указатель на неполный тип, вы получаете поведение undefined. На практике деструктор не может быть вызван.

Мы можем видеть, что на LiveWorkSpace с использованием следующей команды и образца:

// -std=c++11 -Wall -W -pedantic -O2

#include <iostream>

struct ForwardDeclared;

void throw_away(ForwardDeclared* fd) {
   delete fd;
}

struct ForwardDeclared {
   ~ForwardDeclared() {
      std::cout << "Hello, World!\n";
   }
};

int main() {
   ForwardDeclared* fd = new ForwardDeclared();
   throw_away(fd);
}

Диагноз:

Compilation finished with warnings:
 source.cpp: In function 'void throw_away(ForwardDeclared*)':
 source.cpp:6:11: warning: possible problem detected in invocation of delete operator: [enabled by default]
 source.cpp:5:6: warning: 'fd' has incomplete type [enabled by default] 
 source.cpp:3:8: warning: forward declaration of 'struct ForwardDeclared' [enabled by default]
 source.cpp:6:11: note: neither the destructor nor the class-specific operator delete will be called, even if they are declared when the class is defined

Не хотите поблагодарить своего компилятора за то, что предупредил вас;)?

Ответ 3

Я бы сказал, что любая опасность затмевается выигрышами. Есть некоторые, хотя, в основном, связанные с рефакторингом.

  • переименование классов влияет на все форвардные объявления. Это, конечно, также включает в себя, но ошибка генерируется в другом месте, поэтому сложнее обнаружить.
  • движущиеся классы от namespace к другому, в сочетании с директивами using, могут нанести ущерб (таинственные ошибки, трудно обнаружить и исправить) - конечно, директивы using с самого начала не работают, но код не является совершенным, не так ли? *
  • шаблоны - для пересылки объявить шаблоны (особенно определенные пользователем) вам понадобится подпись, что приведет к дублированию кода.

Рассмотрим

template<class X = int> class Y;
int main()
{
    Y<> * y;
}

//actual definition of the template
class Z
{  
};
template<class X = Z> //vers 1.1, changed the default from int to Z
class Y
{};

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

* Я недавно столкнулся с этим:

Оригинал:

Определение:

//3rd party code
namespace A  
{
   struct X {};
}

и форвардная декларация:

//my code
namespace A { struct X; }

После рефакторинга:

//3rd party code
namespace B
{
   struct X {};
}
namespace A
{
   using ::B::X;
}

Это явно показало, что мой код недействителен, но ошибка не была на самом деле, и исправление было, по меньшей мере, подозрительным.

Ответ 4

Если указатель на неполный тип класса передается в delete, перегрузка operator delete может быть упущена.

Чтобы все, что у меня было... и быть укушенным, вам придется сделать это, но ничего больше в исходном файле, который бы вызвал ошибку компилятора "неполного типа".

РЕДАКТИРОВАТЬ: Следуя примеру других ребят, я бы сказал, что трудность (может считаться опасностью) заключается в обеспечении того, чтобы передовая декларация фактически соответствовала реальной декларации. Для функций и шаблонов списки аргументов необходимо синхронизировать.

И вам нужно удалить декларацию вперед при удалении вещи, которую она объявляет, или она сидит вокруг и прорывает пространство имен. Но даже в таких случаях компилятор укажет на него в сообщениях об ошибках, если это мешает.

Более опасная опасность не имеет прямого заявления. Основным недостатком вложенных классов является то, что они не могут быть объявлены вперед (ну, они могут находиться внутри оболочки класса, но это только краткий).

Ответ 5

Единственная опасность переадресации - это когда вы выполняете форвардную декларацию за пределами заголовка или в не разделяемом заголовке, а подпись переднего объявления отличается от фактической подписи того, что было объявлено в прямом порядке. Если вы сделаете это в extern "C", не будет искажения имени для проверки подписи во время ссылки, поэтому вы можете столкнуться с поведением undefined, когда подписи не совпадают.

Ответ 6

Другая опасность форвардных деклараций заключается в том, что это облегчает нарушение правила Единого определения. Предположим, что у вас есть ah forward, объявляющий class B (который предположительно находится в bh и b.cpp), но внутри a.cpp вы фактически включаете b2.h, который объявляет другой class B, чем bh, затем вы получаете undefined.

Ответ 7

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

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

Ответ 8

Первый способ - переупорядочить наши вызовы функций, поэтому add определяется перед main:

Таким образом, к моменту, когда main() вызывает add(), он уже будет знать, что такое add. Поскольку это такая простая программа, это изменение относительно легко сделать. Однако в большой программе было бы очень утомительно пытаться расшифровать функции, называемые функциями, которые могли бы быть объявлены в правильном порядке.