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

StackOverflow https://stackoverflow.com/questions/127290

  •  02-07-2019
  •  | 
  •  

Вопрос

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

Код С:

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" вокруг кода C++, и каждый код находится внутри своей собственной единицы компиляции.

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

Это было полезно?

Решение

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

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

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

Другие советы

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

фу* m_pfoo;

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

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

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

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

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

Технически вы можете делать все, что хотите, при условии, что вы не вызываете неопределенное поведение, например.например, удаление указателя на производный класс по указателю на подобъект его базового класса.Вам даже не нужно 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 и пока производный объект не имеет собственных деструкторов, вам может повезти.

/РЕДАКТИРОВАТЬ:С другой стороны, если код используется только так, как в вопросе, наследование не имеет преимущества перед композицией.Просто следуйте советам m_pGladiator.

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

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

foo *pFoo = new bar;
delete pFoo;

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

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

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

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

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

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

class Bar
{
private:
   Foo  mFoo;
};

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

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

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

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

Трудно представить, как в «реальном мире» это может иметь место.

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

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

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

Альтернативный подход, поведение которого четко определено, состоит в том, чтобы сделать «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_;
};
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top