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

С# в С++ 'Gotchas'

Я разрабатываю проект, который я должен полностью развивать на С++. Мне нужно разработать оболочку и открыть некоторые С++-функции в моем приложении С#. Я был инженером С# с самого начала .NET и имел очень мало опыта в С++. Мне все еще кажется очень чуждым при попытке понять синтаксис.

Есть ли что-нибудь, что сбивает меня с ног, что помешает мне просто подобрать С++ и пойти на это?

4b9b3361

Ответ 1

В С++ так много ошибок, что я не могу их перечислить. Сделайте поиск по "С# vs С++". Несколько основных вещей, которые нужно знать: В С++:

  • struct и класс в основном одинаковы (видимость по умолчанию для структуры является общедоступной, она закрыта для класса).
  • Обе структуры и класс могут быть созданы либо в куче, либо в стеке.
  • Вам нужно управлять кучей самостоятельно. Если вы создаете что-то с "новым", вы должны удалить его вручную в какой-то момент.
  • Если производительность не является проблемой, и у вас очень мало данных для перемещения, вы можете избежать проблемы с управлением памятью, имея все в стеке и используя ссылки (& operator).
  • Научитесь работать с .h и .cpp. Неразрешенный внешний может быть хуже кошмара.
  • Вы не должны вызывать виртуальный метод из конструктора. Компилятор никогда не скажет вам об этом.
  • Корпус коммутатора не обеспечивает "перерыв" и по умолчанию проходит.
  • Существует не такая вещь, как интерфейс. Вместо этого у вас есть класс с чистыми виртуальными методами.
  • С++ поклонники - опасные люди, живущие в пещере и выживающие на свежей крови программистов С#/java. Поговорите с ними о своем любимом языке.

Ответ 2

Сбор мусора!

Помните, что каждый раз, когда вы new объект, вы должны нести ответственность за вызов delete.

Ответ 3

Есть много различий, но самое большое, что я могу думать о том, что программисты из Java/С# всегда ошибаются и не понимают, что они ошибаются, это семантика значений С++.

В С# вы привыкли использовать new в любое время, когда хотите создать объект. И всякий раз, когда мы говорим о экземпляре класса, мы действительно имеем в виду "ссылку на экземпляр класса". Foo x = y не копирует объект y, он просто создает другую ссылку на любой объект y.

В С++ существует четкое различие между локальными объектами, выделенными без new (Foo f или Foo f(x, y)) и динамически выделенными (Foo* f = new Foo() или Foo* f = new Foo(x, y)). И в терминах С# все тип значения. Foo x = y фактически создает копию самого объекта Foo.

Если вам нужна эталонная семантика, вы можете использовать указатели или ссылки: Foo& x = y создает ссылку на объект y. Foo* x = &y создает указатель на адрес, на котором находится y. И копирование указателя делает именно это: он создает другой указатель, который указывает на то, на что указывает указатель оригинала. Таким образом, это похоже на ссылочную семантику С#.

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

Динамически выделенные объекты не уничтожаются до тех пор, пока вы не назовете delete.

До сих пор ты, наверное, со мной. Новоприбывших на С++ преподают это довольно скоро. Трудная часть заключается в том, что это значит, как это влияет на ваш стиль программирования:

В С++ по умолчанию должно быть создано локальные объекты. Не выделяйте new, если вам не нужно.

Если вам нужны динамически распределенные данные, возложите на них ответственность класса. A (очень) упрощенный пример:

class IntArrayWrapper {
  explicit IntArrayWrapper(int size) : arr(new int[size]) {} // allocate memory in the constructor, and set arr to point to it
  ~IntArrayWrapper() {delete[] arr; } // deallocate memory in the destructor

  int* arr; // hold the pointer to the dynamically allocated array
};

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

Итак, скажем, нам нужен массив из целых чисел x, вместо этого:

void foo(int x){
  int* arr = new int[x];
  ... use the array ...
  delete[] arr; // if the middle of the function throws an exception, delete will never be called, so technically, we should add a try/catch as well, and also call delete there. Messy and error-prone.
}

вы можете сделать это:

void foo(int x){
  IntArrayWrapper arr(x);
  ... use the array ...
  // no delete necessary
}

Конечно, это использование локальных переменных вместо указателей или ссылок означает, что объекты копируются довольно много:

Bar Foo(){
  Bar bar;
  ... do something with bar ...
  return bar;
}

в приведенном выше примере мы возвращаем копию объекта bar. Мы могли бы вернуть указатель или ссылку, но поскольку экземпляр, созданный внутри функции, выходит из области действия и уничтожается в момент возвращения функции, мы не могли бы указать на это. Мы могли бы использовать new для выделения экземпляра, который переживает эту функцию, и возвращать ей функцию - и затем мы получаем все головные боли управления памятью, выясняя, чья ответственность заключается в том, чтобы удалить объект, и когда это произойдет. Это не очень хорошая идея.

Вместо этого класс bar должен быть просто спроектирован так, чтобы его копирование делало то, что нам нужно. Возможно, он должен внутренне вызвать new, чтобы выделить объект, который может жить так долго, как нам это нужно. Затем мы могли бы сделать копирование или присвоение "украсть" этот указатель. Или мы могли бы реализовать какую-то схему подсчета ссылок, где копирование объекта просто увеличивает счетчик ссылок и копирует указатель - который затем должен быть удален не тогда, когда отдельный объект будет уничтожен, а когда последний объект будет уничтожен, а счетчик ссылок достигает 0.

Но часто мы можем просто выполнить глубокую копию и полностью клонировать объект. Если объект включает динамически выделенную память, мы выделяем больше памяти для копии. Это может показаться дорогим, но компилятор С++ хорош в устранении ненужных копий (и на самом деле в большинстве случаев разрешено исключать операции копирования, даже если они имеют побочные эффекты).

Если вы хотите избежать копирования еще больше, и вы готовы мириться с немного более неуклюжим использованием, вы можете включить "семантику перемещения" в своих классах, а также (или вместо) "семантику копирования". Это стоит использовать в этой привычке, потому что (a) некоторые объекты не могут быть легко скопированы, но их можно перемещать (например, класс Socket), (b) это шаблон, установленный в стандартной библиотеке, и (c) получение поддержка языков в следующей версии.

С помощью семантики перемещения вы можете использовать объекты как своего рода "переносимый" контейнер. Это содержимое, которое движется. В текущем подходе это делается путем вызова swap, который свопит содержимое двух объектов одного типа. Когда объект выходит из области видимости, он разрушается, но если вы сначала поменяете его содержимое на ссылочный параметр, уничтожение содержимого будет уничтожено при завершении области. Поэтому вам не обязательно проходить весь путь и использовать ссылки, подсчитанные интеллектуальными указателями, чтобы позволить возвращать сложные объекты из функций. Чувство возникает из-за того, что вы не можете их действительно вернуть - вам нужно поменять их на ссылочный параметр (несколько похожий на параметр ref в С#). Но поддержка языка в следующей версии С++ будет решать это.

Итак, самый большой С# для С++, который я могу придумать: не делайте указатели по умолчанию. Используйте семантику значений и вместо этого адаптируйте свои классы для того, чтобы вести себя так, как вы хотите, когда они будут скопированы, созданы и уничтожены.

Несколько месяцев назад я попытался написать серию сообщений в блогах для людей в вашей ситуации:
Часть 1
Часть 2
Часть 3

Я не на 100% доволен тем, как они оказались, но вы все равно можете найти их полезными.

И когда вы чувствуете, что никогда не поймете хватку на указателях,

Ответ 4

Нет проверок времени выполнения

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

Философия С# подчеркивает правильность; все поведение должно быть четко определенным, и в таких случаях он выполняет проверку предварительных условий во время выполнения и генерирует четко определенные исключения, если они не работают.

Философия С++ подчеркивает эффективность и идею, что вы не должны платить за все, что вам может не понадобиться. В таких случаях ничто не будет проверено для вас, поэтому вы должны либо сами проверить предварительные условия, либо разработать свою логику, чтобы они были правдой. В противном случае код будет иметь поведение undefined, что означает, что он может (более или менее) делать то, что вы хотите, он может упасть или может повредить полностью несвязанные данные и вызвать ошибки, которые ужасно трудно отследить.

Ответ 5

Просто чтобы бросить некоторые другие, которые еще не были упомянуты другими ответами:

const: С# имеет ограниченное представление о const. В С++ важна "const-correctness". Методы, не изменяющие их ссылочные параметры, должны принимать const-ссылки, например.

void func(const MyClass& x)
{
    // x cannot be modified, and you can't call non-const methods on x
}

Функции-члены, которые не изменяют объект, должны быть отмечены как const, т.е.

int MyClass::GetSomething() const // <-- here
{
    // Doesn't modify the instance of the class
    return some_member;
}

Это может показаться ненужным, но на самом деле очень полезно (см. следующий пункт на временных страницах), а иногда и требуется, поскольку библиотеки, такие как STL, полностью const-correct, и вы не можете передавать const-вещи в непостоянные вещи (не используйте const_cast! Ever!). Это также полезно для тех, кто знает, что что-то не изменится. Лучше подумать об этом так: если вы опустите const, вы говорите, что объект будет изменен.

Временные объекты. В качестве еще одного упомянутого ответа С++ гораздо важнее значения-семантики. Временные объекты могут быть созданы и уничтожены в выражениях, например:

std::string str = std::string("hello") + " world" + "!";

Здесь первый + создает временную строку с "hello world". Второй + объединяет временное с "!", Предоставляя временное содержащее "привет мир!", Которое затем копируется на str. По завершении инструкции временные файлы немедленно уничтожаются. Чтобы еще больше усложнить ситуацию, С++ 0x добавляет ссылки rvalue для решения этой проблемы, но этот выход выходит за рамки этого ответа!

Вы также можете привязывать временные объекты к ссылкам const (другая полезная часть const). Рассмотрим предыдущую функцию еще раз:

void func(const MyClass& x)

Это можно вызывать явно с временным MyClass:

func(MyClass()); // create temporary MyClass - NOT the same as 'new MyClass()'!

Создается экземпляр MyClass в стеке, func2 обращается к нему, а затем временный MyClass автоматически уничтожается после возврата func. Это удобно и обычно очень быстро, так как куча не задействована. Примечание "new" возвращает указатель, а не ссылку, и требует соответствующего "удаления". Вы также можете напрямую назначать временные ссылки на ссылки const:

const int& blah = 5;   // 5 is a temporary
const MyClass& myClass = MyClass(); // creating temporary MyClass instance
// The temporary MyClass is destroyed when the const reference goes out of scope

Константы и временные ссылки часто встречаются в хорошем стиле С++, и то, как эти работы сильно отличаются от С#.

RAII, безопасность исключений и детерминированные деструкторы. Это на самом деле полезная функция С++, возможно, даже преимущество над С#, и это стоит прочитать, так как это также хороший стиль С++. Я не буду здесь его освещать.

Наконец, я просто поставлю этот - это указатель, а не ссылка:)

Ответ 6

Традиционными камнями преткновения для людей, приходящих на С++ с С# или Java, являются управление памятью и полиморфное поведение:

  • Хотя объекты всегда живут в куче и являются мусором, собранным на С#/Java, вы можете иметь объекты в статическом хранилище, стеке или куче ( "свободный магазин" в стандартном разговоре) на С++. Вы должны очистить материал, который вы выделяете из кучи (new/delete). Неоценимый метод борьбы с этим - RAII.
  • Наследование/полиморфизм работают только через указатель или ссылку в С++.

Есть много других, но они, вероятно, помогут вам в первую очередь.

Ответ 8

Заголовочные файлы!. Вы спросите: "Почему мне нужно каждый раз писать декларации методов?"

Ответ 9

Указатели и распределение памяти

... Я тоже парень из С#, и я все еще пытаюсь обернуть голову правильными методами памяти в C/С++.

Ответ 10

Вот краткий обзор Managed С++ здесь. Статья о написании неуправляемой оболочки с помощью Managed С++ здесь. Здесь есть еще одна статья о смешении Unmanaged с управляемым кодом С++ здесь.

Используя Managed С++, IMHO упростит использование в качестве моста для мира С# и наоборот.

Надеюсь, это поможет, С наилучшими пожеланиями, Том.

Ответ 11

Смачивание сырой памяти.. Снимаются союзы, memsets и другие записи прямой памяти. Каждый раз, когда кто-то пишет в память как последовательность байтов (в отличие от объектов), вы теряете большую часть способности рассуждать о коде.

Ответ 12

Связь

Связывание с внешними библиотеками не так простенькое, как в .Net, $DEITY поможет вам, если вы смешаете что-то, скомпилированное с разными вкусами одного и того же msvcrt (debug, multithread, unicode...)

Строка

И вам придется иметь дело с строками Unicode vs Ansi, это не совсем то же самое.

Удачи:)

Ответ 13

Самое большое отличие - это семантика ссылок С# (для большинства типов) и семантика значений С++. Это означает, что объекты копируются гораздо чаще, чем на С#, поэтому важно обеспечить правильное копирование объектов. Это означает реализацию конструктора копирования и operator= для любого класса с деструктором.

Ответ 14

Следующее не должно отговариваться каким-либо образом: D

С++ - это минное поле Gotcha, оно относительно ручное, если вы не используете шаблоны и STL - и просто используйте ориентацию объектов, но даже тогда это монстр. В этом случае объектно-ориентированное программирование (а не объектно-ориентированное программирование) делает его даже укротителем - часто эта форма С++ применяется в некоторых проектах (т.е. Не использует никаких функций, которые даже имеют шансы наивно использоваться).

Однако вы должны изучить все эти вещи, так как это очень мощный язык, если вам удастся пройти по минному полю. Если вы хотите узнать о gotcha, вам лучше получить книги от Herb Sutter, Scott Myers и Bjarne Stroustrup. Также систематически переходя С++ FAQ Lite поможет вам понять, что действительно требуется 10 или около того книг, чтобы превратиться в хорошего программиста на С++.