Можно ли разделить структуру C на C ++ и использовать указатели на структуру в C-коде? - PullRequest
24 голосов
/ 24 сентября 2008

Есть ли побочный эффект при этом:

код C:

struct foo {
      int k;
};

int ret_foo(const struct foo* f){ 
    return f.k; 
}

C ++ код:

class bar : public foo {

   int my_bar() { 
       return ret_foo( (foo)this ); 
   }

};

Вокруг кода C ++ есть extern "C", и каждый код находится внутри собственного модуля компиляции.

Это переносимо через компиляторы?

Ответы [ 10 ]

28 голосов
/ 24 сентября 2008

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

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

У меня было , если бы это случилось с gcc и g ++. Я работал над проектом, который использовал несколько больших структур. К сожалению, g ++ упаковал структуры значительно слабее, чем gcc, что вызвало значительные проблемы с совместным использованием объектов между кодом C и C ++. В конце концов нам пришлось вручную устанавливать упаковку и вставлять отступы, чтобы код C и C ++ обрабатывал структуры одинаково. Обратите внимание, что эта проблема может возникнуть независимо от подклассов. На самом деле мы не делали подклассы структуры C в этом случае.

11 голосов
/ 24 сентября 2008

Я, конечно, не рекомендую использовать такие странные подклассы. Было бы лучше изменить свой дизайн, чтобы использовать композицию вместо наследования. Просто сделайте один член

foo * m_pfoo;

в баре класса, и он будет делать то же самое.

Другая вещь, которую вы можете сделать, это создать еще один класс FooWrapper, содержащий структуру внутри себя с соответствующим методом getter. Тогда вы можете создать подкласс оболочки. Таким образом, проблема с виртуальным деструктором исчезла.

3 голосов
/ 24 сентября 2008

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

Вы можете использовать наследование для расширения C-структур с помощью методов и конструкторов.

Образец:

struct POINT { int x, y; }
class CPoint : POINT
{
public:
    CPoint( int x_, int y_ ) { x = x_; y = y_; }

    const CPoint& operator+=( const POINT& op2 )
    { x += op2.x; y += op2.y; return *this; }

    // etc.
};

Расширение структур может быть "большим" злом, но это не то, что вам запрещено делать.

3 голосов
/ 24 сентября 2008

«Никогда не производи от конкретных классов». - Саттер

«Сделайте не листовые классы абстрактными». - Мейерс

Подклассы неинтерфейсных классов просто неправильны. Вы должны реорганизовать свои библиотеки.

Технически, вы можете делать то, что вы хотите, если вы не вызываете неопределенное поведение, e. например, удаление указателя на производный класс указателем на его подобъект базового класса. Вам даже не нужно extern "C" для кода C ++. Да, это портативный. Но это плохой дизайн.

2 голосов
/ 24 сентября 2008

Это совершенно законно, и вы можете увидеть это на практике с классами MFC CRect и CPoint. CPoint наследуется от POINT (определяется в windef.h), а CRect наследуется от RECT. Вы просто украшаете объект с помощью функций-членов. Пока вы не расширяете объект большим количеством данных, все в порядке. На самом деле, если у вас есть сложная структура C, которую сложно инициализировать по умолчанию, расширение ее с помощью класса, содержащего конструктор по умолчанию, является простым способом решения этой проблемы.

Даже если вы сделаете это:

foo *pFoo = new bar;
delete pFoo;

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

Вам также не нужно заключать объект C ++ в 'extern "C", так как вы фактически не передаете функции C ++ типа в функции C.

2 голосов
/ 24 сентября 2008

Ух ты, это зло.

Это переносимо через компиляторы?

Определенно нет. Учтите следующее:

foo* x = new bar();
delete x;

Для того, чтобы это работало, деструктор foo должен быть виртуальным, что явно не так. Пока вы не используете new и если у производного объекта нет пользовательских деструкторов, вам может повезти.

/ EDIT: с другой стороны, если код используется только так, как в вопросе, наследование не имеет преимущества перед композицией. Просто следуйте советам, которые дает m_pGladiator.

1 голос
/ 24 сентября 2008

Это будет работать, и, к сожалению, НО вы не можете использовать какие-либо виртуальные функции (включая деструкторы).

Я бы порекомендовал вместо этого, чтобы Bar содержал Foo.

class Bar
{
private:
   Foo  mFoo;
};
1 голос
/ 24 сентября 2008

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

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

0 голосов
/ 24 сентября 2008

Это, вероятно, будет работать, но я не верю, что это гарантировано. Ниже приводится цитата из ISO C ++ 10/5:

Подобъект базового класса может иметь компоновку (3.7), отличную от компоновки наиболее производного объекта того же типа.

Трудно понять, как в "реальном мире" это может быть на самом деле.

РЕДАКТИРОВАТЬ:

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

EDIT:

Альтернативный подход, и тот, чье поведение хорошо определено, состоит в том, чтобы сделать 'foo' членом 'bar' и предоставить оператор преобразования там, где это необходимо.

class bar {
public:    
   int my_bar() { 
       return ret_foo( foo_ ); 
   }

   // 
   // This allows a 'bar' to be used where a 'foo' is expected
   inline operator foo& () {
     return foo_;
   }

private:    
  foo foo_;
};
0 голосов
/ 24 сентября 2008

Я не понимаю, почему вы просто не делаете ret_foo методом-членом. Ваш текущий способ делает ваш код очень сложным для понимания. Что такого сложного в использовании реального класса, во-первых, с помощью переменной-члена и методов get / set?

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

...