Можно ли подклассифицировать C-структуру на С++ и использовать указатели на структуру в C-коде?

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

C-код:

struct foo {
      int k;
};

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

Код С++:

class bar : public foo {

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

};

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

Является ли это переносимым для всех компиляторов?

22
задан 24 сент. '08 в 16:56
источник поделиться
10 ответов

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

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

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

26
ответ дан 24 сент. '08 в 19:30
источник

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

foo * m_pfoo;

в классе бар, и он выполнит ту же работу.

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

9
ответ дан 24 сент. '08 в 17:04
источник

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

"Внесите неклассические классы абстрактными". - Мейерс

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

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

3
ответ дан 24 сент. '08 в 17:21
источник

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

Вы можете использовать наследование для расширения 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 сент. '08 в 17:31
источник

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

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

foo *pFoo = new bar;
delete pFoo;

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

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

2
ответ дан 24 сент. '08 в 18:43
источник

Ничего себе, это зло.

Является ли это переносимым для всех компиляторов?

Скорее всего, нет. Рассмотрим следующее:

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

Чтобы это сработало, foo destructor должен быть виртуальным, которого явно нет. Если вы не используете new и до тех пор, пока производный объект d не имеет настраиваемых деструкторов, вам может быть повезло.

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

2
ответ дан 24 сент. '08 в 17:00
источник

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

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

1
ответ дан 24 сент. '08 в 17:07
источник

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

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

class Bar
{
private:
   Foo  mFoo;
};
1
ответ дан 24 сент. '08 в 17:08
источник

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

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

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

EDIT:

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

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 сент. '08 в 17:20
источник

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

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

0
ответ дан 24 сент. '08 в 17:10
источник

Другие вопросы по меткам или Задайте вопрос