iterators - Contenedor C para clase C++ con asignación de pila
libreria list c++ (7)
Aquí hay otro enfoque, que puede o no ser aceptable, dependiendo de los detalles de la aplicación. Aquí básicamente ocultamos la existencia de la instancia de TheClass del código C y encapsulamos todos los escenarios de uso de TheClass en una función de contenedor. Esto se volverá inmanejable si el número de tales escenarios es demasiado grande, pero de lo contrario puede ser una opción.
El contenedor C:
extern "C" void do_magic()
{
TheClass object;
object.magic();
}
El envoltorio es trivialmente llamado de C.
Actualización 17/02/2016:
Dado que desea una solución con un objeto TheClass con estado, puede seguir la idea básica de su enfoque original, que se mejoró en otra respuesta. Aquí hay otro giro en ese enfoque, donde se verifica el tamaño del marcador de posición de la memoria, proporcionado por el código C, para garantizar que sea lo suficientemente grande como para contener una instancia de TheClass.
Yo diría que el valor de tener una instancia de TheClass asignada por la pila es cuestionable aquí, y es una llamada de juicio dependiendo de los detalles de la aplicación, por ejemplo, el rendimiento. Aún debe llamar a la función de desasignación, que a su vez llama al destructor, manualmente, ya que es posible que TheClass asigne recursos que deben liberarse.
Sin embargo, si tener TheClass asignado a una pila es importante, aquí hay otro boceto.
El código de C ++ a ser envuelto, junto con el envoltorio:
#include <new>
#include <cstring>
#include <cstdio>
using namespace std;
class TheClass {
public:
TheClass(int i) : x(i) { }
// cout doesn''t work, had to use puts()
~TheClass() { puts("Deleting TheClass!"); }
int magic( const char * s, int i ) { return 123 * x + strlen(s) + i; }
private:
int x;
};
extern "C" TheClass * create_the_class( TheClass * self, size_t len )
{
// Ensure the memory buffer is large enough.
if (len < sizeof(TheClass)) return NULL;
return new(self) TheClass( 3 );
}
extern "C" int do_magic( TheClass * self, int l )
{
return self->magic( "abc", l );
}
extern "C" void delete_the_class( TheClass * self )
{
self->~TheClass(); // ''delete self;'' won''t work here
}
El código C:
#include <stdio.h>
#define THE_CLASS_SIZE 10
/*
TheClass here is a different type than TheClass in the C++ code,
so it can be called anything else.
*/
typedef struct TheClass { char buf[THE_CLASS_SIZE]; } TheClass;
int do_magic(TheClass *, int);
TheClass * create_the_class(TheClass *, size_t);
void delete_the_class(TheClass * );
int main()
{
TheClass mem; /* Just a placeholder in memory for the C++ TheClass. */
TheClass * c = create_the_class( &mem, sizeof(TheClass) );
if (!c) /* Need to make sure the placeholder is large enough. */
{
puts("Failed to create TheClass, exiting.");
return 1;
}
printf("The magic result is %d/n", do_magic( c, 232 ));
delete_the_class( c );
return 0;
}
Este es solo un ejemplo artificial para propósitos de ilustración. Espero que sea útil. Puede haber problemas sutiles con este enfoque, por lo que probar en su plataforma específica es muy importante.
Algunas notas adicionales:
THE_CLASS_SIZE en el código C es simplemente el tamaño de un búfer de memoria en el que se
TheClass
instanciaTheClass
C ++; estamos bien siempre que el tamaño del buffer sea suficiente para contenerTheClass
C ++Debido a que
TheClass
en C es solo un marcador de posición de memoria, también podríamos usar unvoid *
, posiblementetypedef
''d, como el tipo de parámetro en las funciones de contenedor en lugar deTheClass
. Volveríamos areinterpret_cast
en el código del contenedor, lo que en realidad aclararía el código:
los punteros aTheClass
de C se reinterpretan esencialmente comoTheClass
C ++ de todos modos.- No hay nada que evite que el código C pase un
TheClass*
a las funciones del contenedor que en realidad no apuntan a la instanciaTheClass
C ++. Una forma de resolver esto es almacenar punteros para inicializar correctamente las instancias de C ++TheClass
en algún tipo de estructura de datos en el código C ++ y regresar a los identificadores de código C que se pueden usar para buscar estas instancias. - Para usar
cout
s en el contenedor de C ++, necesitamos vincularlo con la lib estándar de C ++ cuando construimos un archivo ejecutable. Por ejemplo, si el código C se compila enmain.o
y C ++ enlib.o
, entonces en Linux o Mac haríamosgcc -o junk main.o lib.o -lstdc++
.
Digamos que tenemos una biblioteca C ++ con una clase como esta:
class TheClass {
public:
TheClass() { ... }
void magic() { ... }
private:
int x;
}
El uso típico de esta clase incluiría la asignación de la pila:
TheClass object;
object.magic();
Necesitamos crear un contenedor C para esta clase. El enfoque más común se ve así:
struct TheClassH;
extern "C" struct TheClassH* create_the_class() {
return reinterpret_cast<struct TheClassH*>(new TheClass());
}
extern "C" void the_class_magic(struct TheClassH* self) {
reinterpret_cast<TheClass*>(self)->magic();
}
Sin embargo, requiere asignación de pila, lo que claramente no es deseable para una clase tan pequeña.
Estoy buscando un enfoque para permitir la asignación de la pila de esta clase desde el código C. Esto es lo que puedo pensar:
struct TheClassW {
char space[SIZEOF_THECLASS];
}
void create_the_class(struct TheClassW* self) {
TheClass* cpp_self = reinterpret_cast<TheClass*>(self);
new(cpp_self) TheClass();
}
void the_class_magic(struct TheClassW* self) {
TheClass* cpp_self = reinterpret_cast<TheClass*>(self);
cpp_self->magic();
}
Es difícil poner contenido real de la clase en los campos de la estructura. No podemos simplemente incluir el encabezado C ++ porque C no lo entendería, por lo que nos obligaría a escribir encabezados C compatibles. Y esto no siempre es posible. Creo que las bibliotecas C realmente no necesitan preocuparse por el contenido de las estructuras.
El uso de este contenedor se vería así:
TheClassW object;
create_the_class(&object);
the_class_magic(&object);
Preguntas:
- ¿Este enfoque tiene algún peligro o desventaja?
- ¿Hay un enfoque alternativo?
- ¿Hay envolturas existentes que utilizan este enfoque?
Así es como resolvería el problema (la idea básica es dejar que C y C ++ interpreten la misma memoria y los nombres de manera diferente):
TheClass.h:
#ifndef THECLASS_H_
#define THECLASS_H_
#include <stddef.h>
#define SIZEOF_THE_CLASS 4
#ifdef __cplusplus
class TheClass
{
public:
TheClass();
~TheClass();
void magic();
private:
friend void createTheClass(TheClass* self);
void* operator new(size_t, TheClass*) throw ();
int x;
};
#else
typedef struct TheClass {char _[SIZEOF_THE_CLASS];} TheClass;
void create_the_class(struct TheClass* self);
void the_class_magic(struct TheClass* self);
void destroy_the_class(struct TheClass* self);
#endif
#endif /* THECLASS_H_ */
TheClass.cpp:
TheClass::TheClass()
: x(0)
{
}
void* TheClass::operator new(size_t, TheClass* self) throw ()
{
return self;
}
TheClass::~TheClass()
{
}
void TheClass::magic()
{
}
template < bool > struct CompileTimeCheck;
template < > struct CompileTimeCheck < true >
{
typedef bool Result;
};
typedef CompileTimeCheck< SIZEOF_THE_CLASS == sizeof(TheClass) >::Result SizeCheck;
// or use static_assert, if available!
inline void createTheClass(TheClass* self)
{
new (self) TheClass();
}
extern "C"
{
void create_the_class(TheClass* self)
{
createTheClass(self);
}
void the_class_magic(TheClass* self)
{
self->magic();
}
void destroy_the_class(TheClass* self)
{
self->~TheClass();
}
}
La función createTheClass es sólo para amistad. Quería evitar que las funciones de C wrapper fueran visibles públicamente dentro de C ++. Cogí la variante de matriz del TO, porque considero que esto es más legible que el enfoque alloca. Probado con:
C Principal:
#include "TheClass.h"
int main(int argc, char*argv[])
{
struct TheClass c;
create_the_class(&c);
the_class_magic(&c);
destroy_the_class(&c);
}
Así es como uno puede hacerlo de manera segura y portátil.
// C++ code
extern "C" {
typedef void callback(void* obj, void* cdata);
void withObject(callback* cb, void* data) {
TheClass theObject;
cb(&theObject, data);
}
}
// C code:
struct work { ... };
void myCb (void* object, void* data) {
struct work* work = data;
// do whatever
}
// elsewhere
struct work work;
// initialize work
withObject(myCb, &work);
Hay peligros de alineación. Pero tal vez no en tu plataforma. La reparación de esto puede requerir código específico de la plataforma o interoperabilidad C / C ++ que no esté estandarizada.
Diseño inteligente, tiene dos tipos. En C, es struct TheClass;
. En C ++, struct TheClass
tiene un cuerpo.
Cree una struct TheClassBuff{char buff[SIZEOF_THECLASS];};
TheClass* create_the_class(struct TheClassBuff* self) {
return new(self) TheClass();
}
void the_class_magic(struct TheClass* self) {
self->magic();
}
void the_class_destroy(struct TheClass* self) {
self->~TheClass();
}
Se supone que C crea el buff, luego crea un handle e interactúa con él. Ahora, por lo general, eso no es necesario, ya que la reinterpretación del puntero al ClassBuff funcionará, pero creo que es un comportamiento indefinido técnicamente.
Lo que hice en igual situación es algo así como: (Omito static_cast, extern "C")
class.h:
class TheClass {
public:
TheClass() { ... }
void magic() { ... }
private:
int x;
}
class.cpp
<actual implementation>
class_c_wrapper.h
void* create_class_instance(){
TheClass instance = new TheClass();
}
void delete_class_instance(void* instance){
delete (TheClass*)instance;
}
void magic(void* instance){
((TheClass*)instance).magic();
}
Ahora, dijiste que necesitas asignación de pila. Para esto puedo sugerir la opción de new
rara vez se usa: colocación nueva. Por lo tanto, pasaría el parámetro adicional en create_class_instance()
que apunta a un búfer asignado lo suficiente para almacenar la instancia de clase, pero en la pila.
Puede usar la colocación nueva en combinación de alloca para crear un objeto en la pila. Para Windows hay _malloca . La importancia aquí es que alloca, y malloca alinean la memoria para usted y ajustar el tamaño del operador expone el tamaño de su clase de forma portátil. Tenga en cuenta que en el código C no pasa nada cuando su variable está fuera del alcance. Especialmente no la destrucción de tu objeto.
C Principal
#include "the_class.h"
#include <alloca.h>
int main() {
void *me = alloca(sizeof_the_class());
create_the_class(me, 20);
if (me == NULL) {
return -1;
}
// be aware return early is dangerous do
the_class_magic(me);
int error = 0;
if (error) {
goto fail;
}
fail:
destroy_the_class(me);
}
the_class.h
#ifndef THE_CLASS_H
#define THE_CLASS_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
class TheClass {
public:
TheClass(int me) : me_(me) {}
void magic();
int me_;
};
extern "C" {
#endif
size_t sizeof_the_class();
void *create_the_class(void* self, int arg);
void the_class_magic(void* self);
void destroy_the_class(void* self);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif // THE_CLASS_H
the_class.cc
#include "the_class.h"
#include <iostream>
#include <new>
void TheClass::magic() {
std::cout << me_ << std::endl;
}
extern "C" {
size_t sizeof_the_class() {
return sizeof(TheClass);
}
void* create_the_class(void* self, int arg) {
TheClass* ptr = new(self) TheClass(arg);
return ptr;
}
void the_class_magic(void* self) {
TheClass *tc = reinterpret_cast<TheClass *>(self);
tc->magic();
}
void destroy_the_class(void* self) {
TheClass *tc = reinterpret_cast<TheClass *>(self);
tc->~TheClass();
}
}
editar:
puede crear una macro envoltura para evitar la separación de la creación y la inicialización. no puede usar las macros de estilo do { } while(0)
porque limitará el alcance de la variable. Hay otras formas de evitar esto, pero esto depende en gran medida de cómo maneje los errores en la base de códigos. Una prueba de concepto es a continuación:
#define CREATE_THE_CLASS(NAME, VAL, ERR) /
void *NAME = alloca(sizeof_the_class()); /
if (NAME == NULL) goto ERR; /
// example usage:
CREATE_THE_CLASS(me, 20, fail);
Esto se expande en gcc para:
void *me = __builtin_alloca (sizeof_the_class()); if (me == __null) goto fail; create_the_class(me, (20));;
Vale la pena mantener cada pieza de conocimiento en un solo lugar, por lo que sugeriría hacer un código de clase "parcialmente legible" para C. Se puede emplear un conjunto bastante simple de definiciones de macro para permitir que se haga en palabras cortas y estándar. Además, se puede usar una macro para invocar el constructor y el destructor al principio y al final de la vida del objeto asignado a la pila.
Digamos que incluimos primero el siguiente archivo universal en el código C y C ++:
#include <stddef.h>
#include <alloca.h>
#define METHOD_EXPORT(c,n) (*c##_##n)
#define CTOR_EXPORT(c) void (c##_construct)(c* thisPtr)
#define DTOR_EXPORT(c) void (c##_destruct)(c* thisPtr)
#ifdef __cplusplus
#define CL_STRUCT_EXPORT(c)
#define CL_METHOD_EXPORT(c,n) n
#define CL_CTOR_EXPORT(c) c()
#define CL_DTOR_EXPORT(c) ~c()
#define OPT_THIS
#else
#define CL_METHOD_EXPORT METHOD_EXPORT
#define CL_CTOR_EXPORT CTOR_EXPORT
#define CL_DTOR_EXPORT DTOR_EXPORT
#define OPT_THIS void* thisPtr,
#define CL_STRUCT_EXPORT(c) typedef struct c c;/
size_t c##_sizeof();
#endif
/* To be put into a C++ implementation coce */
#define EXPORT_SIZEOF_IMPL(c) extern "C" size_t c##_sizeof() {return sizeof(c);}
#define CTOR_ALIAS_IMPL(c) extern "C" CTOR_EXPORT(c) {new(thisPtr) c();}
#define DTOR_ALIAS_IMPL(c) extern "C" DTOR_EXPORT(c) {thisPtr->~c();}
#define METHOD_ALIAS_IMPL(c,n,res_type,args) /
res_type METHOD_EXPORT(c,n) args = /
call_method(&c::n)
#ifdef __cplusplus
template<class T, class M, M m, typename R, typename... A> R call_method(
T* currPtr, A... args)
{
return (currPtr->*m)(args...);
}
#endif
#define OBJECT_SCOPE(t, v, body) {t* v = alloca(t##_sizeof()); t##_construct(v); body; t##_destruct(v);}
Ahora podemos declarar nuestra clase (el encabezado es útil tanto en C como en C ++, también)
/* A class declaration example */
#ifdef __cplusplus
class myClass {
private:
int y;
public:
#endif
/* Also visible in C */
CL_STRUCT_EXPORT(myClass)
void CL_METHOD_EXPORT(myClass,magic) (OPT_THIS int c);
CL_CTOR_EXPORT(myClass);
CL_DTOR_EXPORT(myClass);
/* End of also visible in C */
#ifdef __cplusplus
};
#endif
Aquí está la implementación de clase en C ++:
myClass::myClass() {std::cout << "myClass constructed" << std::endl;}
CTOR_ALIAS_IMPL(myClass);
myClass::~myClass() {std::cout << "myClass destructed" << std::endl;}
DTOR_ALIAS_IMPL(myClass);
void myClass::magic(int n) {std::cout << "myClass::magic called with " << n << std::endl;}
typedef void (myClass::* myClass_magic_t) (int);
void (*myClass_magic) (myClass* ptr, int i) =
call_method<myClass,myClass_magic_t,&myClass::magic,void,int>;
y este es un ejemplo de código C que usa
main () {
OBJECT_SCOPE(myClass, v, {
myClass_magic(v,178);
})
}
¡Es corto y funciona! (aquí está la salida)
myClass constructed
myClass::magic called with 178
myClass destructed
Tenga en cuenta que se utiliza una plantilla variadica y esto requiere c ++ 11. Sin embargo, si no desea usarlo, se puede usar una cantidad de plantillas de tamaño fijo.