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


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

Код 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 ); 
   }

};

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

Это переносимый между компиляторами?

10 24

10 ответов:

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

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

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

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

Foo* m_pfoo;

В классе bar и он будет выполнять ту же работу.

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

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

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

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

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

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

Наследование можно использовать для расширения 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.
};
Расширение структур может быть" большим " злом, но это не то, что вам запрещено делать.

Вау, это зло.

Это переносимый между компиляторами?

Определенно нет. Рассмотрим следующее:

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

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

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

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

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

foo *pFoo = new bar;
delete pFoo;

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

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

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

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

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

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

class Bar
{
private:
   Foo  mFoo;
};

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

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

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

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

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

Редактировать:

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

Редактировать:

Альтернативный подход, поведение которого хорошо определено, состоит в том, чтобы сделать " 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_;
};