Если я использую приведение C-Style в своем проекте C ++, стоит ли рефакторинг для приведений C ++? - PullRequest
65 голосов
/ 19 февраля 2011

Я использую приведение в стиле C в своем проекте 15K LOC C ++, 90% случаев для приведений между дочерними и базовыми классами.

Даже когда я читаю, что их использование плохо и что они могут привести к серьезным ошибкам, поскольку они небезопасны по типу, как в C ++, я все же чувствую совершенно нормально и комфортно при использовании им.

До сих пор в моем проекте не было ни одной ошибки, вызванной, например, случайным опечаткой в ​​стиле C - Style. Действительно,

Есть две основные причины, по которым я их не использовал:

  • Я еще недостаточно о них знал
  • Мне не понравился их синтаксис, они более многословны и труднее для меня читать

Мои вопросы:

  • (Почему) Должен ли я реорганизовать свой проект для использования приведения в стиле C ++?
  • Почему я должен использовать приведения в стиле C ++ для своих будущих проектов?

Я использую так же хорошо, как и все другие преимущества, которые предлагает мне C ++, от ООП, включая виртуальные и абстрактные базовые классы, пространства имен, STL и т. Д., Только не новый синтаксис приведения типов. Аргумент " Почему вы тогда просто не используете C? " не работает для меня.

Ответы [ 7 ]

89 голосов
/ 19 февраля 2011

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

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

T* ptr = (T*) var;

Они могут не сразу определить, является ли это

  1. приведением из базового класса к производному классу или наоборот.
  2. Приведение к полосе const Отключение от var
  3. Приведение из целочисленного типа к указателю.

Хотя они, вероятно, могут подобрать этоИсходя из контекста, становится гораздо более очевидным, что происходит, если вы используете приведение типа

T* ptr = static_cast<T*>(var);

или

T* ptr = const_cast<T*>(var);

Еще одна причина, по которой операторы в стиле C ++ предпочитают, заключается в том, что они делаюткод более устойчив к изменениям.Например, предположим, у меня есть эта функция:

void DoSomething(Base* ptr) {
    Derived* derived = (Derived *) ptr;
    DoSomethingElse(derived);
}

Теперь предположим, что я понимаю, что эта функция не должна вносить какие-либо изменения в свой аргумент, поэтому я решил пометить ее const.Например:

void DoSomething(const Base* ptr) {
    Derived* derived = (Derived *) ptr;
    DoSomethingElse(derived);
}

Но теперь у нас есть проблема - мой бросок в стиле C, который раньше был просто понижен, теперь также удаляет const ness.Это может привести к простой ошибке, когда DoSomethingElse изменяет указатель, который я передаю, хотя сам метод DoSomething обещает этого не делать.Если вместо этого я написал этот код как

void DoSomething(Base* ptr) {
    Derived* derived = static_cast<Derived *>(ptr);
    DoSomethingElse(derived);
}

, а затем изменил код, добавив const:

void DoSomething(const Base* ptr) {
    Derived* derived = static_cast<Derived *>(ptr);
    DoSomethingElse(derived);
}

Теперь я получу ошибку компилятора, сообщающую, что мой старый приведениене работает, что может привести меня к обнаружению логической ошибки в коде (а именно, что DoSomethingElse изменяет свой аргумент, поэтому я не могу наивно сделать ptr указателем на const.

Короче говоря, использование операторов приведения в C ++ делает код более читабельным и более легким в обслуживании. Это делает логику кода более явной. И делает код менее подверженным ошибкам, так как компилятор перехватывает ошибки, как и вы.Вы делаете их или позже, когда вернетесь и измените старый код. Я бы порекомендовал в будущем попытаться использовать операторы приведения в стиле C ++ по этим основным причинам.

Что касается того, стоит ли вам возвращаться и пытатьсязаменить ваши текущие приведения в стиле C на приведения в стиле C ++, это действительно ваше дело. В качестве упражнения я бы предложил сделать это просто для того, чтобы попрактиковаться в изучении того, какие типы приведения вы используете.вы можете найти там логическую ошибку, которая сделает поиск стоящим вашего времени!

33 голосов
/ 19 февраля 2011

В компании, в которой я работал, нам когда-то приходилось переносить приложение на несколько миллионов строк кода на новую платформу. То, что начиналось как «просто еще один порт для еще одной платформы Unix» (код, уже запущенный на Windows, OSX и с полдюжины Unix-подобных платформ), оказалось огромной проблемой. IIRC, причина была в том, что эта платформа имела довольно строгие требования к выравниванию , и некоторые из наших приведений отключали аппаратные исключения из-за смещения ,

Это было бы достаточно легко исправить, если бы не тот факт, что определенный процент этих приведений составлял приведений в стиле C . Было невозможно эффективно grep для них. Поиски оказались буквально десятками тысяч строк кода , которые все должны были быть проверены вручную людьми , 99,99% которые были совершенно не имеют значения.
Это не помогло, если бы мы людей имели тенденцию пропускать несколько из этих трудноуловимых бросков, которые затем должны были быть найдены в другом раунде просматривая десятки тысяч строк кода, 99,99% из них точно такие же, как и в первом раунде.

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

Само собой разумеется, что впоследствии было решено заменить все приведения в стиле C их аналогами в C ++. Однако если бы эта политика существовала до того, как нам пришлось сделать этот порт ...

10 голосов
/ 19 февраля 2011

(Почему) Должен ли я реорганизовать свой проект для использования приведения в стиле C ++?

Да. По той же причине вы должны использовать их в будущем.

Почему я должен использовать приведения в стиле C ++ для своих будущих проектов?

Список на самом деле довольно длинный, но я остановлюсь на самом важном.

Во-первых, они четко указывают на цель состава. Читая «static_cast», вы знаете, что автор намеревался выполнить статическое приведение, а не переосмысление или констант.

Во-вторых, они делают одно и только одно. Например, вы не можете случайно выбросить константу.

Их легко искать. Выполните поиск "const_cast", чтобы найти все приведения в / из const. Как бы вы сделали это с C-стилем? Вы не могли.

Они не изменяют тип приведения, когда изменяется семантика локального кода. Например, (Widget*)(blah) изменит молчание на переинтерпретацию, если Widget перестанет наследовать от того типа, на который был указан бла. Кроме того, он преобразуется в приведенную интерпретацию, если определение виджета не доступно локально. Если вместо этого вы будете использовать приведение в новом стиле, вы получите рвоту компилятором, а не молчаливое переосмысление утверждения.

Вы можете выполнять динамическое приведение. Приведения в стиле C не могут этого сделать.

9 голосов
/ 19 февраля 2011

Ну, две части:

(Почему) Должен ли я реорганизовать мой проект для использования приведения в стиле C ++?

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

Почему я должен использовать приведение в стиле C ++ для своих будущих проектов?

Потому что С-броски легко ошибиться.

4 голосов
/ 19 февраля 2011

Если 90% ваших приведений из подкласса в базовый класс, то удаление их будет началом - они не нужны.Если у вас есть много приведений от базы к подпрограмме, то вы делаете что-то еще не так.

Остальное, вы говорите компилятору, что происходит что-то странное, и вы нарушаете правила - либоправила системы типов с reinterpret_cast или const_cast, вы рискуете представить с static_cast меньшим целочисленным типом, или вы нарушаете LSP и вам необходимо знать о конкретном типе подкласса, когда ему передается базовый класс.

Хорошее объяснение этих случаев.

3 голосов
/ 19 февраля 2011
(Why) Should I refactor my project to use C++-style casts?
Why should I use C++-style casts for my future projects?

Есть несколько хороших ответов (включая мою собственную IMO) на второй вопрос по этому вопросу.

По первому вопросу, если бы я был на вашем месте, я бызаменять приведения в стиле C ++ на приведения в стиле C всякий раз, когда я уже редактировал файл.Вероятно, не стоит откладывать день или около того, чтобы отредактировать весь код и заново скомпилировать его, но, вероятно, стоит постепенно улучшать свою кодовую базу, когда вы уже над ней работаете.

Если вывыделите время для перефакторинга, это было бы хорошим кандидатом для этого.

2 голосов
/ 19 февраля 2011

Если бы я был вами, я бы продолжал использовать броски в стиле C.У вас есть веские причины: вы не испытываете предполагаемых недостатков приведения в стиле C, приведения C ++ более многословны, приведения C ++ труднее читать и менее знакомы для вас.

В более общем смысле, как C ++Программист, вы часто будете читать или вам будет сказано, что какой-то способ - это Старый, Неправильный, С способ выполнения вещей, и что вы должны использовать Новый, Правильный, С ++ способ ведения дел.Некоторые примеры:

  • вы должны использовать приведения в стиле C ++ вместо приведения в стиле C
  • вы должны использовать ссылки вместо указателей
  • вы должны использовать ключевое слово "const"очень много
  • вы должны использовать обобщения вместо указателей void или типов:
  • вы должны использовать списки инициализаторов вместо инициализации переменных-членов в теле конструкторов
  • вы должны использовать конструкторы вместофункции инициализации после построения
  • вы никогда не должны использовать макросы

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

Я предлагаю вам просмотреть такой совет с подозрением.Вместо этого подумайте: решает ли использование новой функции C ++ проблему, которая у вас есть на самом деле?Создает ли это новые проблемы (кстати, повышенная детализация кода и снижение читабельности являются проблемами)?Если ответ на первый вопрос - «нет», или ответ на первый вопрос - «да», вам следует придерживаться варианта «Старый, неправильный, С».

Дополнительный комментарий к ясности кода: многословие уменьшает ясность, еслидобавленный текст не помогает читателю понять программу.Если бы я был вами, я бы рассматривал утверждение о том, что приведения в стиле С ++ скептически улучшают ясность кода, поскольку вы, читатель вашей программы, не ощущаете выгоды.пытался использовать новый, правильный, C ++ способ.Я больше не такой догматичный.Эссе по ссылке ниже было важным фактором, изменившим мое мнение.

http://yosefk.com/c++fqa/

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...