Каковы опасности предварительных деклараций?

Более общедоступным способом является вызов get_form в классах Admin. Он также работает и для полей без базы данных. Например, здесь у меня есть поле «_terminal_list» в форме, которое может использоваться в особых случаях для выбора нескольких элементов терминала из get_list (request), а затем фильтрации на основе request.user:

class ChangeKeyValueForm(forms.ModelForm):  
    _terminal_list = forms.ModelMultipleChoiceField( 
queryset=Terminal.objects.all() )

    class Meta:
        model = ChangeKeyValue
        fields = ['_terminal_list', 'param_path', 'param_value', 'scheduled_time',  ] 

class ChangeKeyValueAdmin(admin.ModelAdmin):
    form = ChangeKeyValueForm
    list_display = ('terminal','task_list', 'plugin','last_update_time')
    list_per_page =16

    def get_form(self, request, obj = None, **kwargs):
        form = super(ChangeKeyValueAdmin, self).get_form(request, **kwargs)
        qs, filterargs = Terminal.get_list(request)
        form.base_fields['_terminal_list'].queryset = qs
        return form
24
задан Default 17 January 2013 в 14:40
поделиться

9 ответов

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

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

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

4
ответ дан GameDeveloper 17 January 2013 в 14:40
поделиться

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

... в Стандарте есть хотя бы одно больное место.

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

Мы видим, что на 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

Разве вы не хотите поблагодарить ваших компилятор для предупреждения вас;)?

15
ответ дан Matthieu M. 17 January 2013 в 14:40
поделиться
1115 Я бы сказал, что любая опасность затмевается успехами. Хотя есть некоторые, в основном связанные с рефакторингом.

  • переименование классов влияет на все предварительные объявления. Это, конечно, также входит с включениями, но ошибка генерируется в другом месте, так что ее сложнее обнаружить.
  • перемещение классов из 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;
}

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

7
ответ дан Jacob 17 January 2013 в 14:40
поделиться

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

0
ответ дан Andre Kostur 17 January 2013 в 14:40
поделиться

Я наткнулся на интересный фрагмент в Руководстве по стилю Google C ++

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

Может быть трудно определить, требуется ли предварительное объявление или полное #include. Замена #include на предварительное объявление может молча изменить значение кода:

// b.h:
struct B {};
struct D : B {};

// good_user.cc:
#include "b.h"
void f(B*);
void f(void*);
void test(D* x) { f(x); }  // calls f(B*)

Если заменить #include на прямое decls для B и D, test () вызовет f (void *).

0
ответ дан MMD 17 January 2013 в 14:40
поделиться

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

3
ответ дан dasblinkenlight 17 January 2013 в 14:40
поделиться

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

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

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

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

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

4
ответ дан Potatoswatter 17 January 2013 в 14:40
поделиться

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

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

-2
ответ дан rosedoli 17 January 2013 в 14:40
поделиться

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

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

0
ответ дан EvertW 17 January 2013 в 14:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: